diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..4be6799
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+IndentWidth: 2
+AlwaysBreakTemplateDeclarations: Yes
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b170eb0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# editorconfig
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+quote_type = single
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.md b/.github/ISSUE_TEMPLATE/1_bug_report.md
new file mode 100644
index 0000000..0d60ca3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1_bug_report.md
@@ -0,0 +1,43 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# Report a bug
+Thank you for using Patapata. This is an issue to report any problems or bugs related to Patapata's features. Please provide the steps to reproduce the issue, the results of those steps, logs, Flutter doctor output, and any sample code if available.
+
+## Description
+-
+
+### Reproduction Steps
+-
+
+### Reproduction Steps Result
+-
+
+### Environment
+-
+
+
+### Logs
+-
+
+
+### **Flutter Doctor Result**
+-
+
diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.md b/.github/ISSUE_TEMPLATE/2_feature_request.md
new file mode 100644
index 0000000..52a3c93
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/2_feature_request.md
@@ -0,0 +1,24 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# Feature Request
+Thank you for using Patapata. This is an issue for requesting new features for Patapata. Please provide the details of the feature you would like to add to Patapata.
+
+## Description
+-
+
+
+## Similar Features
+-
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/3_other.md b/.github/ISSUE_TEMPLATE/3_other.md
new file mode 100644
index 0000000..558e9cc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/3_other.md
@@ -0,0 +1,15 @@
+---
+name: Other issue
+about: Create regarding other issues
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# Other Issue
+Thank you for using Patapata. This is an issue for reporting bugs, problems, or making suggestions other than adding new features.
+
+## Description
+-
+
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..917ef9e
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+
+
+# Issue Number
+#{IssueNumber}
+
+
+# Summary
+-
+
\ No newline at end of file
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 0000000..2857fb7
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,58 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Check
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ flutterVersion:
+ - '3.19.3'
+ - '3.16.9'
+ - '3.13.9'
+ - '3.13.0'
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: ${{ matrix.flutterVersion }}
+ channel: 'stable'
+ cache: true
+
+ - name: Setup Go environment
+ uses: actions/setup-go@v4.1.0
+
+ - name: Install dependencies
+ run: |-
+ go install github.com/google/addlicense@latest
+ flutter pub get
+ dart pub global activate melos
+ melos bootstrap
+
+ - name: Analyze project source
+ run: melos run lint:all
+
+ - name: Run tests
+ run: melos run test:all
+
+ - name: Check license headers
+ run: melos run check-license-header
+
+ - name: Test building Android
+ run: melos run build:example_android
+
+ - name: Test building web
+ run: melos run build:example_web
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
new file mode 100644
index 0000000..bc3e793
--- /dev/null
+++ b/.github/workflows/static.yml
@@ -0,0 +1,32 @@
+name: Upload Codecov
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: 'stable'
+
+ - name: Install dependencies
+ run: flutter pub get
+ working-directory: ./packages/patapata_core
+
+ - name: Run tests for coverage
+ run: flutter test --dart-define=IS_TEST=true --no-pub --coverage
+ working-directory: ./packages/patapata_core
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1353e81
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.vscode/*
+.DS_Store
+**/coverage
+pubspec_overrides.yaml
+.dart_tool
+.melos_tool
+.atom
+.idea
+.packages
+.pub
+.symlinks
+pubspec.lock
+
+! .vscode/launch.json
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..6b8f821
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,6 @@
+--indent 2
+--maxwidth 100
+--wrapparameters afterfirst
+--disable sortedImports,unusedArguments,wrapMultilineStatementBraces
+--exclude Pods,**/MainFlutterWindow.swift,**/AppDelegate.swift,**/.symlinks/**
+--swiftversion 5.7
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e4d87c4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..488e9f8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,141 @@
+# Contribution to Patapata
+
+This is a guideline on how to contribute to Patapata. By following these guidelines, anyone can contribute to Patapata.
+
+## Issues
+
+If you have any issues related to Patapata, please report them. When you do, use a template from GitHub Issues.
+
+- Report errors here in this Issue.
+- Share your opinions on questions or feature requests related to Patapata here in this Issue.
+- For any other questions, please use this Issue.
+
+## Pull Request
+
+If you have any modifications related to Patapata, please submit a PR (Pull Request). In this case, please adhere to the following guidelines:
+
+- Submit the PR against the next branch.
+- Fill in the necessary items in the template when creating the PR.
+- Assign it an issue number and appropriate labels.
+- Ensure it meets the test and coverage requirements with `melos run test:all` or manually with `flutter test --dart-define=IS_TEST=true` for all plugins and core.
+- Generally, if you're adding additional Dart files, include their corresponding test code as well.
+
+## Copyright Notice
+
+Patapata uses the MIT License. The rights to this open-source software are owned by GREE, Inc., and the company retains these rights even after the software is publicly released.
+
+Regarding modifications made by individuals other than GREE, Inc., the rights to the modified portions belong to the person who made the modifications.
+
+When creating source code in languages like Dart, Kotlin, Swift, etc., it is essential to include the MIT License at the beginning of the file. Please follow the formatting guidelines specific to the programming language. Below, I'll provide an example of the license statements that are typically included in Dart, Kotlin, and Swift files managed in Patapata:
+
+dart
+
+```dart
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+```
+
+Kotlin
+
+```kotlin
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+```
+
+Swift
+
+```swift
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+```
+
+You can also use the melos command to automatically add the license statement to the file.
+
+`melos run add-license-header`
+
+Note that you will need a working go environment with [addlicense](https://github.com/google/addlicense) installed to use this command.
+
+## Coding Conventions
+
+- We generally follow the [effective-dart](https://dart.dev/guides/language/effective-dart/style) guidelines.
+- We adhere to the [Flutter formatting rules](https://flutter.dev/docs/development/tools/formatting).
+- We follow the rules of the [Linter](https://dart-lang.github.io/linter/lints/index.html).
+
+### Other Coding Conventions
+
+- Start all local variables with 't,' where 't' stands for temporary. We do this to instantly understand if a variable will affect something outside of the current scope without having to manually looking at the declaration.
+
+```dart
+void main() async {
+ final App tApp = createApp();
+ ...
+ tApp.run();
+}
+```
+
+- Iterator-related variables in constructs like `for` loops can remain as 'i' or similar; there's no issue with that.
+
+```dart
+ for (var i = 0; i < 10; i++) {
+ print("count : $i");
+ }
+```
+
+- For internal function names, begin with 'f.' We do this to instantly understand when a function is a local function versus anything else. For example:
+
+```dart
+ void exampleFunc() {
+ fHogeHoge() {
+ ...
+ }
+
+ fHogeHoge();
+ }
+```
+
+- Start private static variables with '_s.’ This is to instantly understand that these variables are static and will possibly affect multiple areas of the codebase as well as not being tied to the current instance.
+
+```dart
+static var _sCounter = 0;
+```
+
+- For constants, begin private ones with '_k' and public ones with 'k.’
+
+```dart
+const _kMyNum = 1;
+const kMyPublicNum = 1;
+```
+
+- After an 'if (...)' statement, always include curly braces '{}' and a newline. Avoid writing it on a single line without curly braces. For if expressions this is not required.
+
+```dart
+// OK
+if (something) {
+ doThat();
+}
+
+// NG
+if (something) doThat();
+
+// OK
+final tArray = [
+ if (something) 2,
+];
+```
+
+## Project Management
+
+Patapata uses [melos](https://melos.invertase.dev/) to manage this monorepository. Please refer to the melos documentation for more information.
+
+## Quality Assurance
+
+- Patapata aims to support the latest Flutter version as much as possible. However, there may be times when it cannot be compatible with that version due to Flutter's update changes.
+- Patapata strives to achieve as close to 100% test code coverage as possible.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ed4e6a9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 GREE, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d97508
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+
+
Patapata
+
+
+
+
+
+
+ A collection of best-practices and tools for building applications quickly and reliably.
+
+
+---
+
+## About
+This package is a plugin for [Patapata](https://pub.dev/packages/patapata_core) that adds support for [Apple Push Notifications](https://developer.apple.com/documentation/usernotifications) to your Patapata app.
+It will automatically integrate with Patapata's RemoteMessaging system and register for push notifications and provide access to APNs tokens.
+
+## Getting started
+
+1. Add the dependency to your `pubspec.yaml` file
+
+```sh
+flutter pub add patapata_apple_push_notifications
+```
+
+2. Import the package
+
+```dart
+import 'package:patapata_apple_push_notifications/patapata_apple_push_notifications.dart';
+```
+
+3. Activate the plugin
+
+```dart
+void main() {
+ App(
+ environment: const Environment(),
+ plugins: [
+ ApplePushNotificationsPlugin(),
+ ],
+ )
+ .run();
+}
+```
+
+## Contributing
+
+Check out the [CONTRIBUTING](https://github.com/gree/patapata/blob/main/CONTRIBUTING.md) guide to get started.
+
+## License
+
+[See the LICENSE file](https://github.com/gree/patapata/blob/main/packages/patapata_apple_push_notifications/LICENSE)
\ No newline at end of file
diff --git a/packages/patapata_apple_push_notifications/analysis_options.yaml b/packages/patapata_apple_push_notifications/analysis_options.yaml
new file mode 100644
index 0000000..193e69d
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ rules:
+ avoid_dynamic_calls: true
+
+analyzer:
+ exclude:
+ - example/**
\ No newline at end of file
diff --git a/packages/patapata_apple_push_notifications/dartdoc_options.yaml b/packages/patapata_apple_push_notifications/dartdoc_options.yaml
new file mode 100644
index 0000000..61adcd2
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/dartdoc_options.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) GREE, Inc.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+dartdoc:
+ include:
+ - patapata_apple_push_notifications
\ No newline at end of file
diff --git a/packages/patapata_apple_push_notifications/example/.gitignore b/packages/patapata_apple_push_notifications/example/.gitignore
new file mode 100644
index 0000000..0fa6b67
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/patapata_apple_push_notifications/example/.metadata b/packages/patapata_apple_push_notifications/example/.metadata
new file mode 100644
index 0000000..cb12308
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b
+ channel: stable
+
+project_type: app
diff --git a/packages/patapata_apple_push_notifications/example/README.md b/packages/patapata_apple_push_notifications/example/README.md
new file mode 100644
index 0000000..902faf2
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/README.md
@@ -0,0 +1,16 @@
+# patapata_apple_push_notifications_example
+
+Demonstrates how to use the patapata_apple_push_notifications plugin.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
+
+For help getting started with Flutter, view our
+[online documentation](https://flutter.dev/docs), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/packages/patapata_apple_push_notifications/example/analysis_options.yaml b/packages/patapata_apple_push_notifications/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at
+ # https://dart-lang.github.io/linter/lints/index.html.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/packages/patapata_apple_push_notifications/example/ios/.gitignore b/packages/patapata_apple_push_notifications/example/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/patapata_apple_push_notifications/example/ios/Flutter/AppFrameworkInfo.plist b/packages/patapata_apple_push_notifications/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..9625e10
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 11.0
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Flutter/Debug.xcconfig b/packages/patapata_apple_push_notifications/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..ec97fc6
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/patapata_apple_push_notifications/example/ios/Flutter/Release.xcconfig b/packages/patapata_apple_push_notifications/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..c4855bf
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/patapata_apple_push_notifications/example/ios/Podfile b/packages/patapata_apple_push_notifications/example/ios/Podfile
new file mode 100644
index 0000000..88359b2
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Podfile
@@ -0,0 +1,41 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/packages/patapata_apple_push_notifications/example/ios/Podfile.lock b/packages/patapata_apple_push_notifications/example/ios/Podfile.lock
new file mode 100644
index 0000000..f0185a9
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Podfile.lock
@@ -0,0 +1,60 @@
+PODS:
+ - connectivity_plus (0.0.1):
+ - Flutter
+ - ReachabilitySwift
+ - device_info_plus (0.0.1):
+ - Flutter
+ - Flutter (1.0.0)
+ - flutter_local_notifications (0.0.1):
+ - Flutter
+ - package_info_plus (0.4.5):
+ - Flutter
+ - patapata_apple_push_notifications (0.0.1):
+ - Flutter
+ - patapata_core
+ - patapata_core (0.0.1):
+ - Flutter
+ - ReachabilitySwift (5.0.0)
+
+DEPENDENCIES:
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
+ - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
+ - Flutter (from `Flutter`)
+ - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
+ - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
+ - patapata_apple_push_notifications (from `.symlinks/plugins/patapata_apple_push_notifications/ios`)
+ - patapata_core (from `.symlinks/plugins/patapata_core/ios`)
+
+SPEC REPOS:
+ trunk:
+ - ReachabilitySwift
+
+EXTERNAL SOURCES:
+ connectivity_plus:
+ :path: ".symlinks/plugins/connectivity_plus/ios"
+ device_info_plus:
+ :path: ".symlinks/plugins/device_info_plus/ios"
+ Flutter:
+ :path: Flutter
+ flutter_local_notifications:
+ :path: ".symlinks/plugins/flutter_local_notifications/ios"
+ package_info_plus:
+ :path: ".symlinks/plugins/package_info_plus/ios"
+ patapata_apple_push_notifications:
+ :path: ".symlinks/plugins/patapata_apple_push_notifications/ios"
+ patapata_core:
+ :path: ".symlinks/plugins/patapata_core/ios"
+
+SPEC CHECKSUMS:
+ connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
+ device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
+ Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
+ package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
+ patapata_apple_push_notifications: d6a9d82ad04d4c1445dafa79f279065ccca3991e
+ patapata_core: ce5b4c8f74bd32c7c405f0d85d4cbbf25cb75da6
+ ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
+
+PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
+
+COCOAPODS: 1.12.1
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.pbxproj b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..4680558
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,542 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+ E9DED2C107BF36CB89FCBCF8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB70CDFBC87CA64226D29D3E /* Pods_Runner.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 084A4BDF948E44A12169D4DA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 6DD3E9A57A2B923D5ADA9AA0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 8ADE9AC30F82395E53769F35 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ FB70CDFBC87CA64226D29D3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E9DED2C107BF36CB89FCBCF8 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 4B45FCEAF3151B6A91C5833B /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 084A4BDF948E44A12169D4DA /* Pods-Runner.debug.xcconfig */,
+ 8ADE9AC30F82395E53769F35 /* Pods-Runner.release.xcconfig */,
+ 6DD3E9A57A2B923D5ADA9AA0 /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 4B45FCEAF3151B6A91C5833B /* Pods */,
+ E082E5A20AEDD012371CEEA5 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ E082E5A20AEDD012371CEEA5 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ FB70CDFBC87CA64226D29D3E /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 1790C79EF3F20CB5FB989075 /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 69595A6DE13247C482AB8BD6 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1430;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 1790C79EF3F20CB5FB989075 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 69595A6DE13247C482AB8BD6 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = dev.patapata.patapataApplePushNotificationsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = dev.patapata.patapataApplePushNotificationsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = dev.patapata.patapataApplePushNotificationsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..b52b2e6
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..21a3cc1
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/AppDelegate.swift b/packages/patapata_apple_push_notifications/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..0f67170
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,18 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/Main.storyboard b/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Info.plist b/packages/patapata_apple_push_notifications/example/ios/Runner/Info.plist
new file mode 100644
index 0000000..93950f9
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ patapata_apple_push_notifications_example
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/packages/patapata_apple_push_notifications/example/ios/Runner/Runner-Bridging-Header.h b/packages/patapata_apple_push_notifications/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/patapata_apple_push_notifications/example/lib/main.dart b/packages/patapata_apple_push_notifications/example/lib/main.dart
new file mode 100644
index 0000000..3d2827c
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/lib/main.dart
@@ -0,0 +1,38 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatefulWidget {
+ const MyApp({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Plugin example app'),
+ ),
+ body: const Center(
+ child: Text('TODO'),
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/example/pubspec.yaml b/packages/patapata_apple_push_notifications/example/pubspec.yaml
new file mode 100644
index 0000000..b64751e
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/example/pubspec.yaml
@@ -0,0 +1,84 @@
+name: patapata_apple_push_notifications_example
+description: Demonstrates how to use the patapata_apple_push_notifications plugin.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+environment:
+ sdk: ">=2.12.0 <3.0.0"
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ patapata_apple_push_notifications:
+ # When depending on this package from a real application you should use:
+ # patapata_apple_push_notifications: ^x.y.z
+ # See https://dart.dev/tools/pub/dependencies#version-constraints
+ # The example app is bundled with the plugin so we use a path dependency on
+ # the parent directory to use the current plugin's version.
+ path: ../
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.2
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^1.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware.
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/packages/patapata_apple_push_notifications/ios/.gitignore b/packages/patapata_apple_push_notifications/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/packages/patapata_apple_push_notifications/ios/Assets/.gitkeep b/packages/patapata_apple_push_notifications/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.h b/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.h
new file mode 100644
index 0000000..b6cee95
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import
+
+@interface PatapataApplePushNotificationsPlugin : NSObject
+@end
diff --git a/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.m b/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.m
new file mode 100644
index 0000000..e9d1bef
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/ios/Classes/PatapataApplePushNotificationsPlugin.m
@@ -0,0 +1,20 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+#import "PatapataApplePushNotificationsPlugin.h"
+#if __has_include()
+#import
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "patapata_apple_push_notifications-Swift.h"
+#endif
+
+@implementation PatapataApplePushNotificationsPlugin
++ (void)registerWithRegistrar:(NSObject*)registrar {
+ [SwiftPatapataApplePushNotificationsPlugin registerWithRegistrar:registrar];
+}
+@end
diff --git a/packages/patapata_apple_push_notifications/ios/Classes/SwiftPatapataApplePushNotificationsPlugin.swift b/packages/patapata_apple_push_notifications/ios/Classes/SwiftPatapataApplePushNotificationsPlugin.swift
new file mode 100644
index 0000000..75b2223
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/ios/Classes/SwiftPatapataApplePushNotificationsPlugin.swift
@@ -0,0 +1,175 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Flutter
+import UIKit
+import patapata_core
+
+public class SwiftPatapataApplePushNotificationsPlugin: NSObject, FlutterPlugin, UNUserNotificationCenterDelegate, PatapataPlugin {
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let tInstance = SwiftPatapataApplePushNotificationsPlugin(registrar: registrar)
+ registrar.registerPatapata(plugin: tInstance)
+ }
+
+ fileprivate let mChannel: FlutterMethodChannel
+ fileprivate var mTokenData: String?
+ fileprivate var mInitialNotification: [String: Any]?
+
+ fileprivate var mEnabled = false
+
+ init(registrar: FlutterPluginRegistrar) {
+ mChannel = FlutterMethodChannel(name: "dev.patapata.patapata_apple_push_notifications", binaryMessenger: registrar.messenger())
+
+ super.init()
+
+ registrar.addMethodCallDelegate(self, channel: mChannel)
+ registrar.addApplicationDelegate(self)
+ }
+
+ public var patapataName = "dev.patapata.patapata_apple_push_notifications"
+
+ public func patapataEnable() {
+ guard !mEnabled else {
+ return
+ }
+
+ mEnabled = true
+
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ UNUserNotificationCenter.current().delegate = self
+ UIApplication.shared.registerForRemoteNotifications()
+
+ if (mTokenData != nil) {
+ notifyUpdatedAPNsToken()
+ }
+ }
+
+ public func patapataDisable() {
+ guard mEnabled else {
+ return
+ }
+
+ mEnabled = false
+ mTokenData = nil
+ UNUserNotificationCenter.current().delegate = nil
+ UIApplication.shared.unregisterForRemoteNotifications()
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "getToken":
+ result(mTokenData)
+ break
+ case "requestPermission":
+ requestPermission(result: result)
+ break
+ case "getInitialNotification":
+ getInitialNotification(result: result)
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ private func requestPermission(result: @escaping FlutterResult) {
+ guard mEnabled else {
+ result(false)
+ return
+ }
+
+ UNUserNotificationCenter.current().requestAuthorization(options: [
+ .alert, .badge, .sound
+ ]) {
+ succeeded, error in
+ result(succeeded)
+ }
+ }
+
+ private func getInitialNotification(result: @escaping FlutterResult) {
+ let tInitialNotification = mInitialNotification
+ mInitialNotification = nil
+ result(tInitialNotification)
+ }
+
+ private func dictionaryFromNotificationResponse(notification: UNNotificationResponse) -> [String : Any] {
+ let tDateFormatter = ISO8601DateFormatter()
+ let tNotification = notification.notification
+
+ return [
+ "actionIdentifier": notification.actionIdentifier,
+ "notification": [
+ "date": tDateFormatter.string(from: tNotification.date),
+ "request": [
+ "identifier": tNotification.request.identifier,
+ "content": [
+ "title": tNotification.request.content.title,
+ "body": tNotification.request.content.body,
+ "userInfo": tNotification.request.content.userInfo
+ ]
+ ]
+ ]
+ ]
+ }
+
+ private func getTokenString(deviceToken: Data) -> String {
+ return deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
+ }
+
+ private func notifyUpdatedAPNsToken() {
+ guard mEnabled else {
+ return
+ }
+
+ mChannel.invokeMethod("updateAPNsToken", arguments: mTokenData)
+ }
+
+ public func applicationDidBecomeActive(_ application: UIApplication) {
+ guard mEnabled else {
+ return
+ }
+
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ }
+
+ public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ mTokenData = getTokenString(deviceToken: deviceToken)
+ notifyUpdatedAPNsToken()
+ }
+
+ public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ }
+
+ public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool {
+ guard mEnabled else {
+ return false
+ }
+
+ if UIApplication.shared.applicationState == .background {
+ // No need to handle BG data
+ completionHandler(.newData)
+ } else {
+ mChannel.invokeMethod("didReceiveRemoteNotification", arguments: userInfo)
+ completionHandler(.noData)
+ }
+
+ return true
+ }
+
+ public func userNotificationCenter(_ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification,
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ completionHandler([.alert])
+ }
+
+ public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+ mInitialNotification = dictionaryFromNotificationResponse(notification: response)
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ mChannel.invokeMethod("didReceiveNotificationResponse", arguments: mInitialNotification)
+ completionHandler()
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/ios/patapata_apple_push_notifications.podspec b/packages/patapata_apple_push_notifications/ios/patapata_apple_push_notifications.podspec
new file mode 100644
index 0000000..c2458f1
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/ios/patapata_apple_push_notifications.podspec
@@ -0,0 +1,25 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint patapata_apple_push_notifications.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'patapata_apple_push_notifications'
+ s.version = '0.0.1'
+ s.summary = 'A new flutter plugin project.'
+ s.description = <<-DESC
+A new flutter plugin project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '10.0'
+
+ s.dependency 'patapata_core'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+end
diff --git a/packages/patapata_apple_push_notifications/lib/patapata_apple_push_notifications.dart b/packages/patapata_apple_push_notifications/lib/patapata_apple_push_notifications.dart
new file mode 100644
index 0000000..7bb960d
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/lib/patapata_apple_push_notifications.dart
@@ -0,0 +1,187 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+library patapata_apple_push_notifications;
+
+import 'package:flutter/services.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_core_libs.dart';
+
+final _logger = Logger('patapata.ApplePushNotificationsPlugin');
+const _channel =
+ MethodChannel('dev.patapata.patapata_apple_push_notifications');
+
+/// A plugin that provides Apple push notification functionality to Patapata.
+class ApplePushNotificationsPlugin extends Plugin {
+ final _messagesController = StreamController.broadcast();
+ final _tokensController = StreamController.broadcast();
+
+ @override
+ String get name => 'dev.patapata.patapata_apple_push_notifications';
+
+ /// Initializes the [ApplePushNotificationsPlugin].
+ @override
+ FutureOr init(App app) async {
+ await super.init(app);
+
+ _channel.setMethodCallHandler(_handleMethodCall);
+
+ return true;
+ }
+
+ @override
+ FutureOr dispose() async {
+ await super.dispose();
+ }
+
+ @override
+ RemoteMessaging createRemoteMessaging() =>
+ ApplePushNotificationsRemoteMessaging(this);
+
+ RemoteMessage _createRemoteMessage(Map response) {
+ final tNotification = Map.castFrom(
+ response['notification'] as Map);
+ final tRequest = Map.castFrom(
+ tNotification['request'] as Map);
+ final tContent = Map.castFrom(
+ tRequest['content'] as Map);
+
+ return RemoteMessage(
+ messageId: tRequest['identifier'],
+ data: Map.castFrom(
+ tContent['userInfo'] as Map),
+ notification: (tContent['title'] as String?)?.isNotEmpty == true ||
+ (tContent['body'] as String?)?.isNotEmpty == true
+ ? RemoteMessageNotification(
+ title: tContent['title'] as String?,
+ body: tContent['body'] as String?,
+ )
+ : null,
+ );
+ }
+
+ Future _handleMethodCall(MethodCall call) async {
+ switch (call.method) {
+ case 'updateAPNsToken':
+ final tToken = call.arguments as String?;
+ _logger.info('updateAPNsToken:$tToken');
+ _tokensController.add(tToken);
+ break;
+ case 'didReceiveRemoteNotification':
+ final tUserInfo = Map.castFrom(
+ call.arguments as Map);
+ _logger.info('didReceiveRemoteNotification:$tUserInfo');
+ // _messagesController.add(RemoteMessage(
+ // messageId: '',
+ // data: tUserInfo,
+ // notification: RemoteMessageNotification(),
+ // ));
+ break;
+ case 'didReceiveNotificationResponse':
+ final tResponse = Map.castFrom(
+ call.arguments as Map);
+ _logger.info('didReceiveNotificationResponse:$tResponse');
+
+ _messagesController.add(_createRemoteMessage(tResponse));
+ break;
+ default:
+ break;
+ }
+ }
+
+ /// A [Stream] that can be listened to for monitoring changes to [RemoteMessage].
+ Stream get messages => _messagesController.stream;
+
+ /// A [Stream] that can be used to monitor changes to the token.
+ Stream get tokenStream => _tokensController.stream;
+
+ /// Returns a Future for the initial Apple notification RemoteMessage.
+ Future getInitialNotification() async {
+ final tNotificationResponse = await _channel
+ .invokeMapMethod('getInitialNotification');
+
+ return tNotificationResponse != null
+ ? _createRemoteMessage(tNotificationResponse)
+ : null;
+ }
+
+ /// Returns a Future for the token required for the application to use RemoteMessage.
+ Future getToken() {
+ return _channel.invokeMethod('getToken');
+ }
+}
+
+/// A class for monitoring remote messages of Apple push notifications.
+class ApplePushNotificationsRemoteMessaging extends RemoteMessaging {
+ final ApplePushNotificationsPlugin _instance;
+
+ StreamSubscription? _onMessageSubscription;
+ StreamSubscription? _onTokensSubscription;
+
+ final _messagesController = StreamController.broadcast();
+ final _tokensController = StreamController.broadcast();
+
+ /// Creates a [ApplePushNotificationsRemoteMessaging].
+ ApplePushNotificationsRemoteMessaging(this._instance);
+
+ /// Initializes the [ApplePushNotificationsRemoteMessaging].
+ @override
+ Future init(App app) async {
+ await super.init(app);
+
+ _onMessageSubscription = _instance.messages.listen(_onMessage);
+ _onTokensSubscription = _instance.tokenStream.listen(_onToken);
+ }
+
+ @override
+ void dispose() {
+ _onMessageSubscription?.cancel();
+ _onMessageSubscription = null;
+ _onTokensSubscription?.cancel();
+ _onTokensSubscription = null;
+ super.dispose();
+ }
+
+ void _onMessage(RemoteMessage message) async {
+ _logger.info(
+ '_onMessage:{messageId:${message.messageId} channel:${message.channel}, data:${message.data}}');
+
+ _messagesController.add(message);
+ }
+
+ void _onToken(String? token) {
+ _logger.info('_onToken:$token');
+
+ _tokensController.add(token);
+ }
+
+ @override
+ Future getInitialMessage() async {
+ return _instance.getInitialNotification();
+ }
+
+ @override
+ Stream get messages => _messagesController.stream;
+
+ @override
+ Stream get tokens => _tokensController.stream;
+
+ @override
+ Future getToken() async {
+ return _instance.getToken();
+ }
+
+ @override
+ // ignore: must_call_super
+ FutureOr listenChannel(String channel) async {
+ return false;
+ }
+
+ @override
+ // ignore: must_call_super
+ FutureOr ignoreChannel(String channel) async {
+ return false;
+ }
+}
diff --git a/packages/patapata_apple_push_notifications/melos_patapata_apple_push_notifications.iml b/packages/patapata_apple_push_notifications/melos_patapata_apple_push_notifications.iml
new file mode 100644
index 0000000..9fc8ce7
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/melos_patapata_apple_push_notifications.iml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/patapata_apple_push_notifications/pubspec.yaml b/packages/patapata_apple_push_notifications/pubspec.yaml
new file mode 100644
index 0000000..3f57829
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/pubspec.yaml
@@ -0,0 +1,26 @@
+name: patapata_apple_push_notifications
+description: This package is a plugin for Patapata that adds support for Apple Push Notifications to your Patapata app.
+version: 1.0.0
+homepage: https://github.com/gree/patapata
+repository: https://github.com/gree/patapata/packages/patapata_apple_push_notifications
+
+environment:
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.13.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ patapata_core: ^1.0.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.2
+
+flutter:
+ plugin:
+ platforms:
+ ios:
+ pluginClass: PatapataApplePushNotificationsPlugin
diff --git a/packages/patapata_apple_push_notifications/test/patapata_apple_push_notifications_test.dart b/packages/patapata_apple_push_notifications/test/patapata_apple_push_notifications_test.dart
new file mode 100644
index 0000000..0efedcd
--- /dev/null
+++ b/packages/patapata_apple_push_notifications/test/patapata_apple_push_notifications_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ test('Empty test pass', () {});
+}
diff --git a/packages/patapata_apps_flyer/.gitignore b/packages/patapata_apps_flyer/.gitignore
new file mode 100644
index 0000000..a247422
--- /dev/null
+++ b/packages/patapata_apps_flyer/.gitignore
@@ -0,0 +1,75 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
diff --git a/packages/patapata_apps_flyer/.metadata b/packages/patapata_apps_flyer/.metadata
new file mode 100644
index 0000000..4311ca2
--- /dev/null
+++ b/packages/patapata_apps_flyer/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b
+ channel: stable
+
+project_type: package
diff --git a/packages/patapata_apps_flyer/CHANGELOG.md b/packages/patapata_apps_flyer/CHANGELOG.md
new file mode 100644
index 0000000..3a10bc5
--- /dev/null
+++ b/packages/patapata_apps_flyer/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- initial release
\ No newline at end of file
diff --git a/packages/patapata_apps_flyer/LICENSE b/packages/patapata_apps_flyer/LICENSE
new file mode 100644
index 0000000..ed4e6a9
--- /dev/null
+++ b/packages/patapata_apps_flyer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 GREE, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/patapata_apps_flyer/README.md b/packages/patapata_apps_flyer/README.md
new file mode 100644
index 0000000..679a39f
--- /dev/null
+++ b/packages/patapata_apps_flyer/README.md
@@ -0,0 +1,70 @@
+
+
Patapata - AppsFlyer
+
+ Add support for AppsFlyer to your Patapata app.
+
+
+
+---
+
+## About
+This package is a plugin for [Patapata](https://pub.dev/packages/patapata_core) that adds support for [AppsFlyer](https://www.appsflyer.com/) to your Patapata app.
+It currently only supports setting the user ID for AppsFlyer via the `setCustomerUserId` method, as well as registering for conversion data callbacks.
+
+## Getting started
+
+1. Add the dependency to your `pubspec.yaml` file
+
+```yaml
+dependencies:
+ patapata_apps_flyer:
+ git:
+ url: git://github.com/gree/patapata.git
+ path: packages/patapata_apps_flyer
+```
+
+2. Import the package
+
+```dart
+import 'package:patapata_apps_flyer/patapata_apps_flyer.dart';
+```
+
+3. Activate the plugin
+
+```dart
+/// This Environment takes AppsFlyer configuration from environment variables.
+/// Pass environment variables to your app using the `--dart-define` flag.
+class Environment extends AppsFlyerPluginEnvironment {
+ const Environment();
+
+ /// The AppsFlyer devKey.
+ @override
+ String get appsFlyerDevKey => const String.fromEnvironment('APPS_FLYER_DEV_KEY');
+
+ /// AppsFlyer's iOS `appId`.
+ @override
+ String get appsFlyerAppIdIOS => const String.fromEnvironment('APPS_FLYER_APP_ID_IOS');
+
+ /// AppsFlyer's Android `appId`.
+ @override
+ String get appsFlyerAppIdAndroid => const String.fromEnvironment('APPS_FLYER_APP_ID_ANDROID');
+}
+
+void main() {
+ App(
+ environment: const Environment(),
+ plugins: [
+ AppsFlyerPlugin(),
+ ],
+ )
+ .run();
+}
+```
+
+## Contributing
+
+Check out the [CONTRIBUTING](https://github.com/gree/patapata/blob/main/CONTRIBUTING.md) guide to get started.
+
+## License
+
+[See the LICENSE file](https://github.com/gree/patapata/blob/main/packages/patapata_apps_flyer/LICENSE)
\ No newline at end of file
diff --git a/packages/patapata_apps_flyer/analysis_options.yaml b/packages/patapata_apps_flyer/analysis_options.yaml
new file mode 100644
index 0000000..193e69d
--- /dev/null
+++ b/packages/patapata_apps_flyer/analysis_options.yaml
@@ -0,0 +1,9 @@
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ rules:
+ avoid_dynamic_calls: true
+
+analyzer:
+ exclude:
+ - example/**
\ No newline at end of file
diff --git a/packages/patapata_apps_flyer/dartdoc_options.yaml b/packages/patapata_apps_flyer/dartdoc_options.yaml
new file mode 100644
index 0000000..0d35966
--- /dev/null
+++ b/packages/patapata_apps_flyer/dartdoc_options.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) GREE, Inc.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+dartdoc:
+ include:
+ - patapata_apps_flyer
\ No newline at end of file
diff --git a/packages/patapata_apps_flyer/ios/.gitignore b/packages/patapata_apps_flyer/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/packages/patapata_apps_flyer/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/packages/patapata_apps_flyer/ios/Assets/.gitkeep b/packages/patapata_apps_flyer/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.h b/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.h
new file mode 100644
index 0000000..1b8779c
--- /dev/null
+++ b/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import
+
+@interface PatapataAppsFlyerPlugin : NSObject
+@end
diff --git a/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.m b/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.m
new file mode 100644
index 0000000..05c3773
--- /dev/null
+++ b/packages/patapata_apps_flyer/ios/Classes/PatapataAppsFlyerPlugin.m
@@ -0,0 +1,20 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+#import "PatapataAppsFlyerPlugin.h"
+#if __has_include()
+#import
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "patapata_apps_flyer-Swift.h"
+#endif
+
+@implementation PatapataAppsFlyerPlugin
++ (void)registerWithRegistrar:(NSObject*)registrar {
+ [SwiftPatapataAppsFlyerPlugin registerWithRegistrar:registrar];
+}
+@end
diff --git a/packages/patapata_apps_flyer/ios/Classes/SwiftPatapataAppsFlyerPlugin.swift b/packages/patapata_apps_flyer/ios/Classes/SwiftPatapataAppsFlyerPlugin.swift
new file mode 100644
index 0000000..9bc6817
--- /dev/null
+++ b/packages/patapata_apps_flyer/ios/Classes/SwiftPatapataAppsFlyerPlugin.swift
@@ -0,0 +1,12 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Flutter
+
+public class SwiftPatapataAppsFlyerPlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let tInstance = SwiftPatapataAppsFlyerPlugin()
+ }
+}
diff --git a/packages/patapata_apps_flyer/ios/patapata_apps_flyer.podspec b/packages/patapata_apps_flyer/ios/patapata_apps_flyer.podspec
new file mode 100644
index 0000000..478b82c
--- /dev/null
+++ b/packages/patapata_apps_flyer/ios/patapata_apps_flyer.podspec
@@ -0,0 +1,23 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint patapata_apps_flyer.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'patapata_apps_flyer'
+ s.version = '0.0.1'
+ s.summary = 'A new flutter plugin project.'
+ s.description = <<-DESC
+A new flutter plugin project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '8.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+end
diff --git a/packages/patapata_apps_flyer/lib/patapata_apps_flyer.dart b/packages/patapata_apps_flyer/lib/patapata_apps_flyer.dart
new file mode 100644
index 0000000..51b2ff3
--- /dev/null
+++ b/packages/patapata_apps_flyer/lib/patapata_apps_flyer.dart
@@ -0,0 +1,97 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+library patapata_apps_flyer;
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:appsflyer_sdk/appsflyer_sdk.dart';
+
+/// Configuration for [AppsflyerSdk].
+mixin AppsFlyerPluginEnvironment {
+ /// AppsFlyer's devKey.
+ String get appsFlyerDevKey;
+
+ /// AppsFlyer's iOS `appId`.
+ String get appsFlyerAppIdIOS;
+
+ /// AppsFlyer's Android `appId`.
+ String get appsFlyerAppIdAndroid;
+}
+
+/// A plugin that provides functionality for AppsFlyer in Patapata.
+class AppsFlyerPlugin extends Plugin {
+ late AppsflyerSdk _sdk;
+
+ /// A reference to the [AppsflyerSdk] instance.
+ AppsflyerSdk get sdk => _sdk;
+
+ /// Initializes the [AppsFlyerPlugin].
+ @override
+ FutureOr init(App app) async {
+ if (app.environment is! AppsFlyerPluginEnvironment) {
+ return false;
+ }
+
+ final tDevKey = _environment.appsFlyerDevKey;
+ final tAppId = defaultTargetPlatform == TargetPlatform.iOS
+ ? _environment.appsFlyerAppIdIOS
+ : _environment.appsFlyerAppIdAndroid;
+
+ if (tDevKey.isEmpty || tAppId.isEmpty) {
+ return false;
+ }
+
+ await super.init(app);
+
+ var tIsDebug = false;
+
+ assert(() {
+ tIsDebug = true;
+ return true;
+ }());
+
+ _sdk = AppsflyerSdk(AppsFlyerOptions(
+ afDevKey: tDevKey,
+ appId: tAppId,
+ showDebug: tIsDebug,
+ timeToWaitForATTUserAuthorization: 60.0,
+ ));
+
+ app.user.addSynchronousChangeListener(_onUserChanged);
+ if (app.permissions.trackingRequested) {
+ await initSdk();
+ } else {
+ app.permissions.trackingStream.first.then((_) => initSdk);
+ }
+
+ return true;
+ }
+
+ AppsFlyerPluginEnvironment get _environment =>
+ app.environment as AppsFlyerPluginEnvironment;
+
+ @override
+ FutureOr dispose() {
+ app.user.removeSynchronousChangeListener(_onUserChanged);
+
+ return super.dispose();
+ }
+
+ /// Initializes the [AppsflyerSdk].
+ Future initSdk() async {
+ await _sdk.initSdk(
+ registerConversionDataCallback: true,
+ registerOnAppOpenAttributionCallback: false,
+ registerOnDeepLinkingCallback: false,
+ );
+ }
+
+ FutureOr _onUserChanged(User user, UserChangeData changes) {
+ _sdk.setCustomerUserId(changes.id ?? '');
+ }
+}
diff --git a/packages/patapata_apps_flyer/pubspec.yaml b/packages/patapata_apps_flyer/pubspec.yaml
new file mode 100644
index 0000000..cff6cb1
--- /dev/null
+++ b/packages/patapata_apps_flyer/pubspec.yaml
@@ -0,0 +1,29 @@
+name: patapata_apps_flyer
+description: This package is a plugin for Patapata that adds support for AppsFlyer to your Patapata app.
+version: 1.0.0
+publish_to: none
+homepage: https://github.com/gree/patapata
+repository: https://github.com/gree/patapata/packages/patapata_apps_flyer
+
+environment:
+ sdk: ">=3.0.0 <4.0.0"
+ flutter: ">=3.13.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ patapata_core: ^1.0.0
+
+ appsflyer_sdk: ^6.11.3
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.2
+
+flutter:
+ plugin:
+ platforms:
+ ios:
+ pluginClass: PatapataAppsFlyerPlugin
diff --git a/packages/patapata_apps_flyer/test/patapata_apps_flyer_test.dart b/packages/patapata_apps_flyer/test/patapata_apps_flyer_test.dart
new file mode 100644
index 0000000..0efedcd
--- /dev/null
+++ b/packages/patapata_apps_flyer/test/patapata_apps_flyer_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ test('Empty test pass', () {});
+}
diff --git a/packages/patapata_core/.github/assets/logo_pata2_horizontal.png b/packages/patapata_core/.github/assets/logo_pata2_horizontal.png
new file mode 100644
index 0000000..c1b3419
Binary files /dev/null and b/packages/patapata_core/.github/assets/logo_pata2_horizontal.png differ
diff --git a/packages/patapata_core/.gitignore b/packages/patapata_core/.gitignore
new file mode 100644
index 0000000..36cf1be
--- /dev/null
+++ b/packages/patapata_core/.gitignore
@@ -0,0 +1,79 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/doc/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+
+# File generated when the golden test fails.
+/test/failures
diff --git a/packages/patapata_core/.metadata b/packages/patapata_core/.metadata
new file mode 100644
index 0000000..616b046
--- /dev/null
+++ b/packages/patapata_core/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: e6b34c2b5c96bb95325269a29a84e83ed8909b5f
+ channel: stable
+
+project_type: package
diff --git a/packages/patapata_core/.vscode/launch.json b/packages/patapata_core/.vscode/launch.json
new file mode 100644
index 0000000..47f82b8
--- /dev/null
+++ b/packages/patapata_core/.vscode/launch.json
@@ -0,0 +1,20 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Patapata Example Material App",
+ "request": "launch",
+ "type": "dart",
+ "program": "example/lib/main.dart"
+ },
+ {
+ "name": "Patapata Example Cupertino App",
+ "request": "launch",
+ "type": "dart",
+ "program": "example/lib/main_cupertino.dart"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/patapata_core/.vscode/settings.json b/packages/patapata_core/.vscode/settings.json
new file mode 100644
index 0000000..da2017b
--- /dev/null
+++ b/packages/patapata_core/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dart.flutterSdkPath": "/Users/jason.parrott/source/flutter-3.7.7"
+}
\ No newline at end of file
diff --git a/packages/patapata_core/CHANGELOG.md b/packages/patapata_core/CHANGELOG.md
new file mode 100644
index 0000000..3a10bc5
--- /dev/null
+++ b/packages/patapata_core/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- initial release
\ No newline at end of file
diff --git a/packages/patapata_core/LICENSE b/packages/patapata_core/LICENSE
new file mode 100644
index 0000000..ed4e6a9
--- /dev/null
+++ b/packages/patapata_core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 GREE, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/patapata_core/README.md b/packages/patapata_core/README.md
new file mode 100644
index 0000000..129a944
--- /dev/null
+++ b/packages/patapata_core/README.md
@@ -0,0 +1,1006 @@
+
+
Patapata
+
+
+
+
+
+
+ A collection of best-practices and tools for building applications quickly and reliably.
+
+
+---
+
+## Table of Contents
+ - [About](#about)
+ - [Supported Platforms](#supported-platforms)
+ - [Getting started](#getting-started)
+ - [Bootstrap](#bootstrap)
+ - [Usage](#usage)
+ - [Environment](#environment)
+ - [App](#app)
+ - [App startup flow](#app-startup-flow)
+ - [Startup Sequence](#startup-sequence)
+ - [User](#user)
+ - [Standard App](#standard-app)
+ - [Internationalization and localization (I18n and L10n)](#internationalization-and-localization-(i18n-and-l10n))
+ - [Logging and error handling](#logging-and-error-handling)
+ - [Notifications](#notifications)
+ - [Utilities](#utilities)
+ - [Finite State Machine](#finite-state-machine)
+ - [Sequential Work Queue](#sequential-work-queue)
+ - [Fake DateTime](#fake-datetime)
+ - [Provider Model](#provider-model)
+ - [Screen Layout](#screen-layout)
+ - [Platform Dialog](#platform-dialog)
+ - [Testing your application](#testing-your-application)
+ - [Testing in the IDE](#testing-in-the-ide)
+ - [Contributing](#contributing)
+ - [License](#license)
+
+## About
+
+Patapata is a framework built on Flutter for creating applications of production quality quickly and reliably.
+It provides a collection of best-practices built directly in to the various APIs so you can build apps that are consistent, stable, and performant.
+
+Patapata Core is the core framework that provides the basic building blocks for your application.
+You will always depend on this package in your application to use Patapata.
+In addition, you can use any of the plugins that Patapata provides to add additional functionality to your application.
+See the [homepage](https://github.com/gree/patapata) for details.
+
+### Supported Platforms
+
+We try to support the newest version of Flutter and will not purposely keep support for older versions if something is deprecated on the Flutter side.
+
+The Patapata team believes that it is important to keep up to date with the latest version of Flutter as in our expierence with real world applications, old versions of Flutter have trouble supporting the newer versions of Android and especially iOS.
+
+Currently, we support Flutter 3.13.0 and above, with a minimum Dart version of 3.0.0 up to 4.0.0.
+
+We officially support Android, iOS fully, and best effort for Web and MacOS.
+Windows and Linux are currently not supported.
+
+## Getting started
+
+To just get the standard Patapata experience and have an app up and running, execute the following in a terminal:
+
+```bash
+flutter create my_app
+cd my_app
+flutter pub add patapata_core
+dart run patapata_core:bootstrap
+```
+
+Note that this will change the minimum Android SDK version to 21 and the minimum iOS version to 12.0.
+
+You should be able to run your application!
+
+### Bootstrap
+
+As in the example above, you can use the `bootstrap` command to quickly get started with Patapata.
+
+This command will:
+- Generate an `Environment` file for you with `I18nEnvironment` and `LogEnvironment` setup by default
+- Generate a `main.dart` file that will create a `Standard App` for you with default settings for almost all of Patapata's features, and a place to add your own `Provider` models to be accessable throughout your application.
+- Generate a `Startup Sequence` that has a splash screen, a fake agreement page, and finally a home page.
+- Generate a default error page for when your app encounters a fatal error.
+- Generate an error class that supports the `PatapataException` system for your app in it's own namespace.
+- Enable [Flutter's deep link system](https://docs.flutter.dev/ui/navigation/deep-linking) that `Standard App` will use for external links to your application
+- Setup the `L10n` system (localization) for your application with default yaml files for English (by default)
+
+## Usage
+
+Patapata has many different systems that all work together to make an application that is easy to maintain and extend.
+Each system of Patapata has [documentation](https://pub.dev/documentation/patapata_core/latest/) that you can read to learn more about it.
+
+Patapata strives to make not just development, but maintence of your application easy and as automatic as possible.
+
+Especially if you use the `bootstrap` command setup, you have automatic logging and reporting of errors to any supported 3rd party service, automatic remote configuration of your application, and localization support ready to go (just add text to your yaml files). You have a splash screen and start up sequence with deep linking out of the box, error handling out of the box, standard features that basically all applications use ready to go (such as package information, device information, local configuration, network information, etc). You also have an analytics system that automatically is sending routing events, page data changing events, lifecycle events, and more.
+
+Developer tools such a Finite State Machine system, a work queue system, a concept of a User, multiple screen size auto layout and more are all available to you.
+
+Tools such as a fake DateTime system exist to make QA testing and backend testing easier as well.
+
+Patapata provides a few must have packages that can be easily accessed without manually importing by importing `package:patapata_core/patapata_core_libs.dart`.
+
+These packages are:
+- [provider](https://pub.dev/packages/provider)
+- [logging](https://pub.dev/packages/logging)
+- [timezone](https://pub.dev/packages/timezone)
+- [visibility_detector](https://pub.dev/packages/visibility_detector)
+- [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications)
+
+### Environment
+
+The `Environment` class is responsible for setting up the environment for your application.
+This class is something you create yourself, and pass to `App` when you create it.
+
+The concept is that your `Environment` class will mixin multiple `Environment` mixins that are provided by Patapata and plugins. Each of these mixins will setup a different part of your application's environment.
+
+All of the `Environment` mixins following a naming convention of `NameEnvironment`, where `Name` is the name of the system that it is setting up.
+
+In general you should try very hard to make your `Environment` class `const` so that it can be used in a `const` context.
+This is important so that any tree shaking that Flutter does will remove any code that is not used in your application as well as for performance reasons.
+
+Also, if you use one of the [String.fromEnvironment](https://api.flutter.dev/flutter/dart-core/String/String.fromEnvironment.html) methods, if you don't use a `const` certain platforms will not function correctly.
+The `String.fromEnvironment` and friends can be used to pass in environment variables to your application at build time via the `--dart-define` flag.
+
+Here is a simple example of an `Environment` class:
+
+```dart
+class Environment
+ with
+ I18nEnvironment,
+ LogEnvironment,
+ SentryEnvironment {
+ /// A base URL for your API.
+ final String apiBaseUrl;
+
+ /// An API key for your API.
+ final String apiKey;
+
+ /// Set's what locales your application supports.
+ @override
+ final List supportedL10ns = const [Locale('en')];
+
+ /// Set's where your application will look for localization files.
+ @override
+ final List l10nPaths = const [
+ 'l10n',
+ ];
+
+ /// The default log level.
+ @override
+ final int logLevel;
+
+ /// Whether or not to print logs to the console.
+ @override
+ final bool printLog;
+
+ /// The Sentry DSN to use if for example you are using Sentry.
+ @override
+ final String sentryDSN;
+
+ /// A function that will be called to setup Sentry.
+ @override
+ final FutureOr Function(SentryFlutterOptions)? sentryOptions = null;
+
+ const MyEnvironment({
+ this.apiBaseUrl = const String.fromEnvironment('API_BASE_URL'),
+ this.apiKey = const String.fromEnvironment('API_KEY'),
+ this.logLevel =
+ const int.fromEnvironment('LOG_LEVEL', defaultValue: -kPataInHex),
+ this.printLog =
+ const bool.fromEnvironment('PRINT_LOG', defaultValue: kDebugMode),
+ this.sentryDSN = const String.fromEnvironment('SENTRY_DSN'),
+ });
+}
+
+void main() async {
+ App(
+ environment: const Environment(),
+ ....
+ )
+ .run();
+}
+```
+
+### App
+
+The [App](https://pub.dev/documentation/patapata_core/latest/patapata_core/App-class.html) class is the main entry point for your application.
+It is responsible for setting up all of Patapata's systems and plugins, and then running your application.
+
+Your entire application will be run inside a special `Zone` that Patapata manages.
+When in this `Zone`, you can access the current `App` via the [getApp](https://pub.dev/documentation/patapata_core/latest/patapata_core/getApp.html) function.
+
+```dart
+getApp().environment.apiBaseUrl;
+```
+
+Your application will also be the child of several `Provider` widgets that are provided by Patapata that allow you to listen to changes via [context.watch](https://pub.dev/documentation/provider/latest/provider/WatchContext/watch.html), [context.select](https://pub.dev/documentation/provider/latest/provider/SelectContext/select.html) and friends.
+
+```dart
+Widget build(BuildContext context) {
+ final tOnline = context.select(
+ (v) => v.connectivity != NetworkConnectivity.none
+ );
+
+ if (tOnline) {
+ return const Text('Online');
+ } else {
+ return const Text('Offline');
+ }
+}
+```
+
+`App` also exposes `Provider`s for:
+- The `App` itself
+- The genericly typed `Environment` version of your `App` as `App`
+- The `Environment`
+- [User](https://pub.dev/documentation/patapata_core/latest/patapata_core/User-class.html)
+ - A class to manage the concept of a 'user' in your application
+- [RemoteConfig](https://pub.dev/documentation/patapata_core/latest/patapata_core/RemoteConfig-class.html)
+ - A class to access remote configuration data for your application
+- [LocalConfig](https://pub.dev/documentation/patapata_core/latest/patapata_core/LocalConfig-class.html)
+ - A class to access locally stored simple key value data for your application
+- [RemoteMessaging](https://pub.dev/documentation/patapata_core/latest/patapata_core/RemoteMessaging-class.html)
+ - A class to access remote messaging data for your application, such as push notifications.
+- [Analytics](https://pub.dev/documentation/patapata_core/latest/patapata_core/Analytics-class.html)
+ - A class to collect and send analytics data for your application
+- The global [AnalyticsContext](https://pub.dev/documentation/patapata_core/latest/patapata_core/AnalyticsContext-class.html)
+- [NetworkInformation](https://pub.dev/documentation/patapata_core/latest/patapata_core/NetworkInformation-class.html) as a [StreamProvider](https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html)
+- [PackageInfoPlugin](https://pub.dev/documentation/patapata_core/latest/patapata_core/PackageInfoPlugin-class.html)
+ - Quick access to all meta information about your application
+- [DeviceInfoPlugin](https://pub.dev/documentation/patapata_core/latest/patapata_core/DeviceInfoPlugin-class.html)
+ - Quick access to all information about the device your application is running on
+
+Some of which are listenable (and therefore watch and selectable).
+
+#### App startup flow
+
+The `App` class goes through a series of steps to setup your application that have specific rules for when things are initialized and when you are allowed to access the various systems of Patapata.
+
+In general, as a developer who is not customizing with `Plugin`s, you should not have to worry about this and can just use the `App` class as is.
+
+`App` goes through the steps are defined in [AppStage](https://pub.dev/documentation/patapata_core/latest/patapata_core/AppStage.html).
+
+1. `setup` - The first stage where the `App` hasn't done any operations and `run` hasn't been executed yet. Nothing is initialized at this point and attempts to access any API except for `the add/remove/hasPlugin` methods will result in undefined behavior. Usually an exception will be thrown.
+2. `bootstrap` - This stage is entered upon execution of `run`. Immediately after entering this stage, the following are executed synchronously:
+ 1. Flutter's services are initialized made useable
+ 2. The special `Zone` that Patapata manages is created an entered
+ 3. The [Log](https://pub.dev/documentation/patapata_core/latest/patapata_core/Log-class.html) system becomes useable
+ 4. Flutter's [ErrorWidget.builder](https://api.flutter.dev/flutter/widgets/ErrorWidget/builder.html) is set to [nonDebugErrorWidgetBuilder](https://pub.dev/documentation/patapata_core/latest/patapata_core/App/nonDebugErrorWidgetBuilder.html)
+ 5. The callback passed to `run` will be executed in a non-asynchronous manner so you are guaranteed that you are still on the same dart task as when `main` was executed During this stage
+3. `initializingPlugins` - The default `Plugin`s and `Plugins`s passed to the `App` are initialized. First, `Plugin`s that have [requireRemoteConfig](https://pub.dev/documentation/patapata_core/latest/patapata_core/Plugin/requireRemoteConfig.html) set to `false` are initialized, allowing for `RemoteConfig` `Plugin`s to be be available and allow remote disabling of `Plugin`s via `RemoteConfig`. If any `Plugin`s fail to initialize, [onInitFailure](https://pub.dev/documentation/patapata_core/latest/patapata_core/App/onInitFailure.html) is called or if null, the error is printed to the console and your app fails to start.
+4. `setupRemoteConfig` - The `RemoteConfig` system is initialized and is useable after this point. Patapata will attempt to fetch the newest remote config data with a 2 second timeout. A timeout will not generate an error and will just delay the start of your application by those 2 seconds.
+5. `initializingPluginsWithRemoteConfig` - The remaining non-initialized `Plugin`s are initialized and remotely removed `Plugin`s are removed (and are never initialized). If any `Plugin`s fail to initialize, [onInitFailure](https://pub.dev/documentation/patapata_core/latest/patapata_core/App/onInitFailure.html) is called or if null, the error is printed to the console and your app fails to start.
+6. `running` - At this stage, all of `Patapata`'s systems are useable. [createAppWidget](https://pub.dev/documentation/patapata_core/latest/patapata_core/App/createAppWidget.html) is wrapped with all the `Provider`s set up by `App`, and wrapped with the `Analytics` system's pointer listener for tracking all pointer events.
+
+If at any point during the above sequence an unhandled error is thrown, `App` will remove the native splash screen, and attempt to report the error to the logging system.
+
+### Startup Sequence
+
+The [StartupSequence](https://pub.dev/documentation/patapata_core/latest/patapata_core/StartupSequence-class.html) class can help you create a startup flow for your application.
+
+You should almost always use this though it is not required.
+
+A Startup Sequence would be a list of actions your app _always_ performs on startup.
+You can provide conditions for each action to be executed, and provide a flow until the processing of the initial actual 'home' page of your application is ready to be shown (or a deep linked page).
+
+The most general use case for this is to show a splash screen, then show a terms of service page, then show a login page, then show the home page.
+
+### User
+
+The [User](https://pub.dev/documentation/patapata_core/latest/patapata_core/User-class.html) class is a class that represents a user of your application.
+It is a `ChangeNotifier` so you can listen to changes to the user's data.
+
+The `User` class is a generic class that you can use to represent any type of user you want.
+
+You can extend this class to make a unique user class for your application, and let Patapata know about it by passing the [userFactory](https://pub.dev/documentation/patapata_core/latest/patapata_core/App/userFactory.html) parameter to `App`.
+
+The `User` is used by the `Analytics` system to track user data, and `Plugin`s can use it to track user data and to provide user specific functionality, log in and out functionality, etc automatically.
+
+For example, the `patapata_firebase_analytics`, `patapata_firebase_crashlytics`, and `patapata_sentry` plugins all use the `User` class to assign properties to the user in their respective systems.
+
+### Standard App
+
+Standard App is an optional, but highly recommended `Plugin` that is enabled by default that you can use to add the concept of a 'page' to your application, and add full support for running an production quality application with very little code.
+
+To use it, you pass either the [StandardMaterialApp](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/StandardMaterialApp-class.html) or [StandardCupertinoApp](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/StandardMaterialApp-class.html) to your `App`'s `createAppWidget` parameter.
+
+From there, you define your standard Flutter `MaterialApp` or `CupertinoApp` settings as normal as well as a list of 'pages' that exist in your application.
+
+Each of these pages is a `StatefulWidget` that extends [StandardPage](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/StandardPage-class.html).
+
+```dart
+void main() {
+ App(
+ createAppWidget: (context, app) => StandardMaterialApp(
+ onGenerateTitle: (context) => l(context, 'title'),
+ pages: [
+ // This is the landing page for the application.
+ StandardPageFactory(
+ create: (_) => HomePage(),
+ links: {
+ // An empty deep link means this page will be shown when the app is opened externally without directly specifying a page.
+ r'': (match, uri) {},
+ },
+ linkGenerator: (pageData) => '',
+ // Home will _always_ exist in the navigation stack with this setting.
+ groupRoot: true,
+ ),
+ // This is just an example of another simple page definition.
+ StandardPageFactory(
+ create: (_) => SettingsPage(),
+ links: {
+ r'settings': (match, uri) {},
+ },
+ linkGenerator: (pageData) => 'settings',
+ ),
+ // This is an example of a page that has a page data object.
+ StandardPageFactory(
+ create: (_) => SearchPage(),
+ links: {
+ // When 'search' as a deep link is opened, this page will be shown,
+ // mapping the uri data to the required page data object.
+ r'search': (match, uri) {
+ return SearchPageData(
+ query: uri.queryParameters['q'] ?? '',
+ reverseSort: uri.queryParameters['r'] == '1',
+ );
+ },
+ },
+ // This regenerates the deep link for this page based off the current page data.
+ linkGenerator: (pageData) => Uri(
+ path: 'search',
+ queryParameters: {
+ 'q': pageData.query,
+ 'r': pageData.reverseSort ? '1' : '0',
+ },
+ ).toString(),
+ ),
+ ],
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ ),
+ ),
+ )
+ .run();
+}
+
+/// This is the simplest page definition.
+class HomePage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'home.title')),
+ ),
+ body: Center(
+ child: Text(l(context, 'home.body')),
+ ),
+ );
+ }
+}
+
+class SearchPageData {
+ final String query;
+ final bool reverseSort;
+
+ const SearchPageData({
+ required this.query,
+ this.reverseSort = false,
+ });
+}
+
+/// This is a page that has a page data object.
+class SearchPage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'search.title')),
+ ),
+ body: Center(
+ // You can access the pageData anywhere.
+ child: Text(pageData.query),
+ ),
+ );
+ }
+}
+```
+
+You can also define a page that returns a result to whoever if opened it.
+
+```dart
+void checkWhatUserWants() {
+ if (await context.goWithResult) {
+ // The user said yes.
+ } else {
+ // The user said no.
+ }
+}
+
+/// This time use [StandardPageWithResult] instead of [StandardPage].
+/// The final generic type is the return type.
+class AskUserPage extends StandardPageWithResult {
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ children: [
+ Text(l(context, 'ask.body')),
+ ElevatedButton(
+ onPressed: () {
+ // The traditional, not type safe way
+ Navigator.pop(context, true);
+ },
+ child: Text(l(context, 'ask.yes')),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ // The new type safe way.
+ // Set the result any time you want.
+ pageResult = false;
+
+ // Then remove the current route later.
+ context.removeRoute();
+ // or Navigator.pop(context);
+ },
+ child: Text(l(context, 'ask.no')),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+void main() {
+ App(
+ createAppWidget: (context, app) => StandardMaterialApp(
+ onGenerateTitle: (context) => l(context, 'title'),
+ pages: [
+ // Use this factory for result pages.
+ StandardPageWithResultFactory(
+ create: (_) => AskUserPage(),
+ ),
+ ],
+ ),
+ )
+ .run();
+}
+```
+
+Quite often, for example, in a search page, you can change the original page data object and want to update the deep link to the current page. `StandardPage` has a `pageData` property that you can set to update the deep link. The `StandardPage` itself is also set in a provider so you can access it from anywhere in your page's widget tree. This allows child widgets to access the page data as well.
+
+```dart
+class SearchPageData {
+ final String query;
+ final bool reverseSort;
+
+ const SearchPageData({
+ required this.query,
+ this.reverseSort = false,
+ });
+}
+
+final _logger = Logger('SearchPage');
+
+class SearchPage extends StandardPage {
+ @override
+ void onPageData() {
+ // If you want to do something every time the page data changes,
+ // you can override this method.
+ _logger.info('pageData changed: $pageData');
+ }
+
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'search.title')),
+ ),
+ body: Column(
+ children: [
+ ElevatedButton(
+ onPressed: () {
+ // This will update the deep link to the current page.
+ // As well as fire off related analytics events.
+ setState(() {
+ pageData = SearchPageData(
+ query: pageData.query,
+ reverseSort: !pageData.reverseSort,
+ );
+ });
+ },
+ child: Text(l(context, 'search.toggleSort')),
+ ),
+ Text('${pageData.query}: ${pageData.reverseSort}'),
+ ],
+ ),
+ );
+ }
+}
+```
+
+A `StandardPage` automatically sets up several analytics features related to page navigation and lifecycle events, as well as when page data changes. Watch the flow of analytics events in your debug log to see what is happening.
+
+You may often want to customize your entire application but need access to `MaterialApp`'s various features such as `Theme`, `MediaQuery` and more. You may also want to provide a global user interface that wraps all of your pages.
+
+To do all of this, use the [routableBuilder](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/StandardMaterialApp/routableBuilder.html) parameter of `MaterialStandardApp` and `CupertinoStandardApp`.
+
+```dart
+void main() {
+ App(
+ createAppWidget: (context, app) => StandardMaterialApp(
+ onGenerateTitle: (context) => l(context, 'title'),
+ pages: [
+ // your pages here
+ ],
+ routableBuilder: (context, child) {
+ return Stack(
+ children: [
+ child,
+ Positioned(
+ bottom: 0,
+ right: 0,
+ width: 100,
+ child: ElevatedButton(
+ onPressed: () {
+ // You can use this context to navigate.
+ // However, Navigator.of will not work.
+ // The reason is [child] is the [Navigator].
+ context.go(null);
+ },
+ child: Text(l(context, 'mypage')),
+ )
+ ),
+ ],
+ );
+ },
+ ),
+ )
+ .run();
+}
+```
+
+`StandardPage` also keeps track of your page's active and inactive lifecycle events.
+A page is 'active' when it is the top page in the navigation stack. A page is 'inactive' when it is not the top page in the navigation stack.
+
+```dart
+class MyPage extends StandardPage {
+ @override
+ void onActive(bool first) {
+ // This will be called when the page becomes active.
+ // [first] will be true on the first time a page becomes active.
+ // Usually that is when the page is first created.
+ super.onActive();
+ }
+
+ @override
+ void onInactive() {
+ // This will be called when the page becomes inactive.
+ super.onInactive();
+ }
+
+ @override
+ void onRefocus() {
+ // This will be called when the page is already active and is navigated to again.
+ }
+}
+```
+
+To enable 100% automatic handling of deep links at startup, be sure to use [StartupSequence](https://pub.dev/documentation/patapata_core/latest/patapata_core/StartupSequence-class.html) and enable Flutter's default deep link handling.
+All of this is done if you use the `bootstrap` command.
+
+## Internationalization and localization (I18n and L10n)
+
+Patapata has a built in system for internationalization and localization.
+It will first of all automatially initialize the [timezone](https://pub.dev/packages/timezone) package so you can use all of it's features out of the box.
+
+As a small feature, Patapata provides an extension to `DateTime` that provides a few common methods for DateTime string formatting commonly used with APIs.
+- [toUTCIso8601StringNoMSUS](https://pub.dev/documentation/patapata_core/latest/patapata_core/DateDateTimeExtension/toUTCIso8601StringNoMSUS.html)
+- [asDateString](https://pub.dev/documentation/patapata_core/latest/patapata_core/DateDateTimeExtension/asDateString.html)
+
+The localization system of Patapata is a core feature that every app should heavily be using.
+It is based on writing yaml files that contain your localized strings as a tree of key value pairs.
+
+It supports Flutter's [MessageFormat](https://api.flutter.dev/flutter/message_format/MessageFormat-class.html) system, which is a subset of the ICU MessageFormat syntax that can handle plurals and select statements (for genders, etc), as well just simple string interpolation.
+
+The yaml files can be hot reloaded and so development is easy and fast.
+
+You use the system with the [l](https://pub.dev/documentation/patapata_core/latest/patapata_core/l.html) function.
+
+```dart
+Text(l(context, 'page1.title'));
+```
+
+Languages will change automatically when the user changes the language of their device.
+
+## Logging and error handling
+
+Logging is accomplished with dart's standard [logging](https://pub.dev/packages/logging) package.
+
+Patapata hooks in to the root Logger and provides a 'reporting' system you and plugins can use to filter, transform, and send to 3rd party services.
+
+```dart
+getApp().log.addFilter((report) {
+ // Only log things that have errors attached.
+ return switch (report.error) {
+ null => null,
+ _ => report,
+ };
+});
+```
+
+```dart
+getApp().log.reports.listen((report) {
+ // Do something with this report.
+});
+```
+
+Logging to console is automatically disabled in release builds, and Patapata disables debugPrint and print for release builds automatically as well for security reasons.
+
+Patapata also provides a system for handling errors in your application.
+
+If you make all of your errors inherit from [PatapataException](https://pub.dev/documentation/patapata_core/latest/patapata_core/PatapataException-class.html), the logging system and various other systems can automatically perform actions when errors occur. They also will use the L10n system to localize errors.
+Errors are also namespaced to that each section of your code or each plugin can have it's own error namespace and therefore error code to show to the user for quick user support.
+
+## Notifications
+
+Patapata currently uses [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) for local notifications.
+
+Patapata will automatically set up the package for you with decent default settings.
+To use it in your application, `import package:patapata_core/patapata_core_libs.dart` and follow the documentation for [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) to display notifications. You should be able to jump right in to executing the API to show a notification.
+
+If you use `StandardApp`, notifications will automatically be wired to open deep links in your application or, if you want to handle a custom notification yourself with `StandardApp`, you can add a [link handler](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/StandardAppPlugin/addLinkHandler.html).
+
+If you do not use `StandardApp`, you can use the [NotificationPlugin's notification stream](https://pub.dev/documentation/patapata_core/latest/patapata_core/NotificationsPlugin/notifications.html) directly.
+
+```dart
+getApp().getPlugin()?.notifications.listen((notification) {
+ // Do something with the notification.
+});
+```
+
+## Utilities
+
+There are a few utilities that Patapata provides that you can use in your application.
+
+### Finite State Machine
+
+Patapata has a [LogicStateMachine](https://pub.dev/documentation/patapata_core/latest/patapata_core/LogicStateMachine-class.html) class that you can use to create a finite state machine for your application. [StartupSequence](https://pub.dev/documentation/patapata_core/latest/patapata_core/StartupSequence-class.html) uses this class to manage it's state.
+
+### Sequential Work Queue
+
+Patapata has a [SequentialWorkQueue](https://pub.dev/documentation/patapata_core/latest/patapata_core/SequentialWorkQueue-class.html) class that you can use to create a work queue that will execute jobs in order, one at a time.
+
+It optionally supports adding jobs that can cancel previous jobs, including stopping the execution of actual dart code with callbacks to allow for cleanup.
+
+### Fake DateTime
+
+Patapata exposes a global getter called [now](https://pub.dev/documentation/patapata_core/latest/patapata_core/now.html) that you can use to get the current date and time.
+
+The definition of 'the current date and time' can be changed by using [setFakeNow](https://pub.dev/documentation/patapata_core/latest/patapata_core/setFakeNow.html), with options to persist the fake time across app restarts as well as having `now` to return the elapsed time since the fake time was set.
+
+This is useful for testing and debugging, as well as syncing your application to a 'server time'.
+
+We recommend using `now` instead of `DateTime.now()` for all uses of the current date and time in your application, except for when something relies on the user's actual local device time, or a time that must be accurate to an external source.
+
+### Provider Model
+
+Often while designing a model based system in dart, the 'model' needs to execute code asynchroniously to complete a modification to itself. For example, a model that needs to fetch data from a server.
+
+In these cases, it is very possible for that same model to get another request to update itself again with newer values.
+
+Sometimes, you want to cancel the previous request and only use the latest request. Sometimes, you want to queue up the requests and execute them in order. While other times, you want to make the second request invalid and not execute it at all.
+
+Setting up a system like this is error prone and time consuming.
+
+Patapata provides a class called [ProviderModel](https://pub.dev/documentation/patapata_core/latest/patapata_core/ProviderModel-class.html) that you can use to easily create a model that can handle all of these cases.
+
+It supports concepts such as 'variables' that are managed and 'transactions' that can be either queued, cancelled, or invalidated. We call these 'transactions' 'batches'.
+
+```dart
+class MyModel extends ProviderModel {
+ final _key = ProviderLockKey('forUpdating');
+
+ late final _myVariable = createVariable('defaultValueHere');
+ String get myVariable => _myVariable.unsafeValue;
+
+ late final _myCounter = createVariable(0);
+ int get myCounter => _myCounter.unsafeValue;
+
+ /// Update [myVariable] and [myCounter] 'atomically'.
+ /// If this is called in succession before the previous
+ /// execution finishes, the second execution will cancel
+ /// the first, and only the last value will be committed.
+ Future updateMyVariable(String newValue) {
+ return lock(
+ _key,
+ (batch) async {
+ // Increment the counter.
+ // At this point, we haven't commited the results
+ // to the model, so any access to [myCounter] will still
+ // not be updated.
+ batch.set(_myCounter, batch.get(_myCounter) + 1);
+
+ if (newValue.isEmpty) {
+ batch.cancel();
+
+ return;
+ }
+
+ // We pretend an API will update remote data.
+ // If it fails, we cancel the batch, and once again
+ // nothing will be updated locally.
+ // [blockOverride] is used to prevent the batch from
+ // being cancelled while in the middle of API execution
+ // because we don't want the API classes' Zone execution
+ // stop and not cleanup.
+ // If this batch was cancelled or overriden it will
+ // cancel after this API call finishes.
+ if (!await batch.blockOverride(() => api.updateMyVariable(newValue))) {
+ batch.cancel();
+
+ return;
+ }
+
+ // Set the variable and commit the result to the model.
+ // On commit, anything listening to this
+ // variable will be updated.
+ batch.set(_myVariable, newValue);
+ batch.commit();
+ },
+ overridable: true,
+ override: true,
+ );
+ }
+
+ /// Update [myVariable] and [myCounter] 'atomically'.
+ /// This one will not cancel the previous execution,
+ /// but will instead fail immediately if another
+ /// execution is in progress.
+ bool updateMyVariableOnlyLocally(String newValue) {
+ try {
+ // If another lock is in progress, this will throw immediately.
+ final tBatch = begin(_key);
+
+ tBatch.set(_myCounter, tBatch.get(_myCounter) + 1);
+ tBatch.set(_myVariable, newValue);
+ tBatch.commit();
+
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+}
+
+Widget build(BuildContext context) {
+ return Provider(
+ create: (_) => MyModel(),
+ child: Selector(
+ selector: (context, model) => model.myVariable,
+ builder: (context, myVariable, child) {
+ return Column(
+ children: [
+ Text(myVariable),
+ ElevatedButton(
+ onPressed: () {
+ context.read().updateMyVariable('new value');
+ },
+ child: const Text('Update'),
+ ),
+ ],
+ );
+ },
+ ),
+ );
+}
+```
+
+As you can see, this is fairly complex to setup, but once you have it setup, it is both easy to use, and can be very powerful. It is designed to cover that 1% case where users do strange things to your application, and prevent your application from crashing or entering an invalid state or other odd issues.
+
+### Screen Layout
+
+Patapata has a helper layout Widget that has the capability to layout all child widgets as if the screen was a certain size. After layout, the child widgets will be scaled to fit the screen.
+
+This is very useful for applications that want to have a single design for screen sizes based off breakpoints, and want to scale the design instead of reflow the design for different screen sizes.
+
+See [ScreenLayout](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/ScreenLayout-class.html) for more information.
+
+### Platform Dialog
+
+Patapata has a [PlatformDialog](https://pub.dev/documentation/patapata_core/latest/patapata_widgets/PlatformDialog-class.html) widget that you can use to show a platform specific dialog.
+
+```dart
+PlatformDialog.show(
+ context: context,
+ title: l(context, 'dialog.title'),
+ message: l(context, 'dialog.message'),
+ actions: [
+ PlatformDialogAction(
+ result: () => true,
+ text: l(context, 'dialog.yes'),
+ isDefault: true,
+ ),
+ PlatformDialogAction(
+ result: () => false,
+ text: l(context, 'dialog.no'),
+ ),
+ ],
+);
+```
+
+## Testing your application
+
+Patapata's plugins and features use native APIs and rely on running on real devices to generally work.
+In a testing environment, you can't use the native APIs, so you need to mock them.
+Patapata itself will automatically mock itself if you set the environment variable `IS_TEST` to true.
+
+```bash
+flutter test --dart-define=IS_TEST=true
+```
+
+Once you have set this environment variable, you can use a few tools in your own tests to quickly and easily leaverage Patapata's features in your tests.
+Typically, you would write a test as follows:
+
+```dart
+void main() {
+ // These two lines are required to mock the native APIs
+ // and _must_ be set before any other code is run.
+ TestWidgetsFlutterBinding.ensureInitialized();
+ testSetMockMethodCallHandler = TestDefaultBinaryMessengerBinding
+ .instance.defaultBinaryMessenger.setMockMethodCallHandler;
+
+ // This StreamHandler is necessary when mocking streams from native APIs, and its responses should be handled
+ // in the onListen and onCancel methods of a class that inherits from the MockStreamHandler class.
+ testSetMockStreamHandler = (channel, handler) {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockStreamHandler(
+ channel,
+ _MockStreamHandler(handler),
+ );
+ };
+
+ testWidgets('My App should run', (WidgetTester tester) async {
+ final tApp = createApp(
+ appWidget: StandardMaterialApp(....),
+ startupSequence: StartupSequence(....),
+ plugins: [....],
+ );
+
+ // It is important to await this, otherwise [App] will not be able
+ // to initialize correctly.
+ await tApp.run();
+
+ // Always run your tests in a [runProcess] block.
+ // Flutter's test system runs code in a different Zone
+ // than what you app runs in, and functions like [getApp] or the logging system
+ // require to be run in a Zone that Patapata is managing.
+ await tApp.runProcess(() async {
+ // Always pumpAndSettle to let Patapata finish initializing.
+ await tester.pumpAndSettle();
+
+ // Write your tests here.
+ });
+
+ // You must call this before executing the next test.
+ tApp.dispose();
+ });
+}
+
+// This class is necessary when preparing a mock stream handler.
+class _MockStreamHandler extends MockStreamHandler {
+ _MockStreamHandler(this.handler);
+
+ final TestMockStreamHandler? handler;
+
+ @override
+ void onCancel(Object? arguments) {
+ // This is where you can handle the onCancel event.
+ }
+
+ @override
+ void onListen(Object? arguments, MockStreamHandlerEventSink events) {
+ // This is where you can handle the onListen event.
+ }
+}
+```
+
+If you are a `Plugin` developer and want to mock your own plugin, you can do so by overriding `setMockMethodCallHandler` in your plugin.
+Currently supported by `App`, `Plugin` and `Config`.
+
+Example: TestPlugin
+```dart
+class TestPlugin extends Plugin {
+ @override
+ @visibleForTesting
+ void setMockMethodCallHandler() {
+ testSetMockMethodCallHandler(
+ const MethodChannel('com.mock.testplugin'),
+ (methodCall) async {
+ methodCallLogs.add(methodCall);
+ switch (methodCall.method) {
+ case 'flight':
+ debugPrint('patapata');
+ default:
+ break;
+ }
+ return null;
+ },
+ );
+ }
+}
+```
+
+Note that there are no hard dependencies on the Flutter test package in this code.
+
+Furthermore, when testing events on the custom plugin side, you can conduct tests using the mock event channel `setMockStreamHandler`.
+```dart
+class TestStreamHandlerPlugin extends Plugin {
+ @override
+ @visibleForTesting
+ void setMockStreamHandler() {
+ testSetMockStreamHandler(
+ const EventChannel('com.mock.testplugin'),
+ _TestMockStreamHandler(),
+ );
+ }
+}
+
+class _TestMockStreamHandler extends TestMockStreamHandler {
+ @override
+ void onCancel(Object? arguments) {}
+
+ @override
+ void onListen(Object? arguments, TestMockStreamHandlerEventSink events) {
+ events.success('sucess event');
+ }
+}
+
+```
+
+This can also be written using the inline function `TestMockStreamHandler.inline`.
+
+Example: TestStreamHandlerInlinePlugin
+```dart
+class TestStreamHandlerInlinePlugin extends Plugin {
+ @override
+ @visibleForTesting
+ void setMockStreamHandler() {
+ testSetMockStreamHandler(
+ const EventChannel('com.mock.testplugin'),
+ TestMockStreamHandler.inline(
+ onListen: (_, events) {
+ events.success('sucess event');
+ },
+ ),
+ );
+ }
+}
+```
+
+### Testing in the IDE
+
+If you run tests from an IDE, you can set the environment variable in the IDE's settings.
+Example: for .vscode/settings.json
+```json
+{
+ "dart.flutterTestAdditionalArgs": ["--dart-define=IS_TEST=true"]
+}
+```
+
+## Contributing
+
+Check out the [CONTRIBUTING](https://github.com/gree/patapata/blob/main/CONTRIBUTING.md) guide to get started.
+
+## License
+
+[See the LICENSE file](https://github.com/gree/patapata/blob/main/packages/patapata_core/LICENSE)
\ No newline at end of file
diff --git a/packages/patapata_core/analysis_options.yaml b/packages/patapata_core/analysis_options.yaml
new file mode 100644
index 0000000..da00fd4
--- /dev/null
+++ b/packages/patapata_core/analysis_options.yaml
@@ -0,0 +1,7 @@
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ rules:
+ avoid_dynamic_calls: true
+
+analyzer:
diff --git a/packages/patapata_core/android/.gitignore b/packages/patapata_core/android/.gitignore
new file mode 100644
index 0000000..f6dca24
--- /dev/null
+++ b/packages/patapata_core/android/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+*~
+.externalNativeBuild
+.DS_Store
+**/ndkHelperBin
+**/.cxx
\ No newline at end of file
diff --git a/packages/patapata_core/android/build.gradle b/packages/patapata_core/android/build.gradle
new file mode 100644
index 0000000..a6cb73a
--- /dev/null
+++ b/packages/patapata_core/android/build.gradle
@@ -0,0 +1,76 @@
+group 'dev.patapata.patapata_core'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.7.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.3.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10"
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'org.jetbrains.dokka'
+
+android {
+ compileSdkVersion 33
+
+ compileOptions {
+ coreLibraryDesugaringEnabled true
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ minSdkVersion 21
+ }
+
+ buildTypes {
+ release {
+ // Enables code shrinking, obfuscation, and optimization for only
+ // your project's release build type.
+ minifyEnabled true
+
+ // Includes the default ProGuard rules files that are packaged with
+ // the Android Gradle plugin. To learn more, go to the section about
+ // R8 configuration files.
+ proguardFiles getDefaultProguardFile(
+ 'proguard-android-optimize.txt'),
+ 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.datastore:datastore-preferences:1.0.0"
+
+ // https://github.com/flutter/flutter/issues/110658#issuecomment-1282045767
+ // Crash fix for desugaring which is required by local notifications plugin
+ implementation "androidx.window:window:1.0.0-rc01"
+ implementation "androidx.window:window-java:1.0.0-rc01"
+}
diff --git a/packages/patapata_core/android/gradle.properties b/packages/patapata_core/android/gradle.properties
new file mode 100644
index 0000000..82dfa36
--- /dev/null
+++ b/packages/patapata_core/android/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.builder.sdkDownload=true
diff --git a/packages/patapata_core/android/gradle/wrapper/gradle-wrapper.properties b/packages/patapata_core/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..111c25e
--- /dev/null
+++ b/packages/patapata_core/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
diff --git a/packages/patapata_core/android/proguard-rules.pro b/packages/patapata_core/android/proguard-rules.pro
new file mode 100644
index 0000000..ea6dd79
--- /dev/null
+++ b/packages/patapata_core/android/proguard-rules.pro
@@ -0,0 +1,32 @@
+##---------------Begin: proguard configuration for Gson ----------
+# Gson uses generic type information stored in a class file when working with fields. Proguard
+# removes such information by default, so configure it to keep all of it.
+-keepattributes Signature
+
+# For using GSON @Expose annotation
+-keepattributes *Annotation*
+
+# Gson specific classes
+-dontwarn sun.misc.**
+#-keep class com.google.gson.stream.** { *; }
+
+# Application classes that will be serialized/deserialized over Gson
+-keep class com.google.gson.examples.android.model.** { ; }
+
+# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
+# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
+-keep class * extends com.google.gson.TypeAdapter
+-keep class * implements com.google.gson.TypeAdapterFactory
+-keep class * implements com.google.gson.JsonSerializer
+-keep class * implements com.google.gson.JsonDeserializer
+
+# Prevent R8 from leaving Data object members always null
+-keepclassmembers,allowobfuscation class * {
+ @com.google.gson.annotations.SerializedName ;
+}
+
+# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
+-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
+
+##---------------End: proguard configuration for Gson ----------
\ No newline at end of file
diff --git a/packages/patapata_core/android/settings.gradle b/packages/patapata_core/android/settings.gradle
new file mode 100644
index 0000000..a068de2
--- /dev/null
+++ b/packages/patapata_core/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'patapata_core'
diff --git a/packages/patapata_core/android/src/main/AndroidManifest.xml b/packages/patapata_core/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b2c05dc
--- /dev/null
+++ b/packages/patapata_core/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/Error.kt b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/Error.kt
new file mode 100644
index 0000000..e6f0aa2
--- /dev/null
+++ b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/Error.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package dev.patapata.patapata_core
+
+fun Throwable.toPatapataMap() : Map {
+ return mapOf(
+ "type" to javaClass.name,
+ "message" to message,
+ "stackTrace" to stackTrace.map { it.toString() },
+ "cause" to cause?.run { toPatapataMap() }
+ )
+}
+
+enum class Error {
+ PPE000,
+ PPENLC000
+}
diff --git a/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/NativeLocalConfig.kt b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/NativeLocalConfig.kt
new file mode 100644
index 0000000..b21b798
--- /dev/null
+++ b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/NativeLocalConfig.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package dev.patapata.patapata_core
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.*
+import androidx.datastore.preferences.preferencesDataStore
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+private val Context.nativeLocalConfigStore: DataStore by preferencesDataStore(name = "dev.patapata.native_local_config")
+
+class NativeLocalConfig(private val context: Context, messenger: BinaryMessenger) : PatapataPlugin, MethodChannel.MethodCallHandler {
+ private val mChannel : MethodChannel = MethodChannel(messenger, "dev.patapata.native_local_config")
+ private val mMainScope = CoroutineScope(Dispatchers.Main)
+ private var mJob: Job? = null
+
+ override val patapataName: String
+ get() = "dev.patapata.native_local_config"
+
+ override fun patapataEnable() {
+ mChannel.setMethodCallHandler(this)
+
+ mJob = mMainScope.launch {
+ context.nativeLocalConfigStore.data
+ .map {
+ it.asMap().map { entry -> entry.key.name to entry.value }.toMap()
+ }
+ .onEach {
+ withContext(Dispatchers.Main) {
+ mChannel.invokeMethod("syncAll", it)
+ }
+ }
+ .cancellable()
+ .catch {
+ withContext(Dispatchers.Main) {
+ mChannel.invokeMethod("error", it.toPatapataMap())
+ }
+ }
+ .collect()
+ }
+ }
+
+ override fun patapataDisable() {
+ mJob?.cancel()
+ mJob = null
+ mChannel.setMethodCallHandler(null)
+ super.patapataDisable()
+ }
+
+ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ val tExceptionHandler = CoroutineExceptionHandler { _, exception ->
+ result.error(Error.PPENLC000.name, null, exception.toPatapataMap())
+ }
+
+ mMainScope.launch(tExceptionHandler) {
+ when (call.method) {
+ "reset" -> context.nativeLocalConfigStore.edit {
+ // PreferenceKey's equality only checks the name.
+ it.remove(stringPreferencesKey(call.arguments as String))
+ result.success(null)
+ }
+ "resetMany" -> context.nativeLocalConfigStore.edit { store ->
+ (call.arguments as? List<*>)?.forEach {
+ // PreferenceKey's equality only checks the name.
+ store.remove(stringPreferencesKey(it as String))
+ }
+
+ result.success(null)
+ }
+ "resetAll" -> context.nativeLocalConfigStore.edit {
+ it.clear()
+ result.success(null)
+ }
+ "setBool" -> context.nativeLocalConfigStore.edit {
+ val tArgs = call.arguments as List<*>
+ it[booleanPreferencesKey(tArgs[0] as String)] = tArgs[1] as Boolean
+ result.success(null)
+ }
+ "setInt" -> context.nativeLocalConfigStore.edit {
+ val tArgs = call.arguments as List<*>
+ it[intPreferencesKey(tArgs[0] as String)] = tArgs[1] as Int
+ result.success(null)
+ }
+ "setDouble" -> context.nativeLocalConfigStore.edit {
+ val tArgs = call.arguments as List<*>
+ it[doublePreferencesKey(tArgs[0] as String)] = tArgs[1] as Double
+ result.success(null)
+ }
+ "setString" -> context.nativeLocalConfigStore.edit {
+ val tArgs = call.arguments as List<*>
+ it[stringPreferencesKey(tArgs[0] as String)] = tArgs[1] as String
+ result.success(null)
+ }
+ "setMany" -> context.nativeLocalConfigStore.edit { store ->
+ (call.arguments as? Map<*, *>)?.forEach {
+ val tKey = it.key
+
+ if (tKey is String) {
+ when (val tValue = it.value) {
+ is Boolean -> store[booleanPreferencesKey(tKey)] = tValue
+ is Int -> store[intPreferencesKey(tKey)] = tValue
+ is Double -> store[doublePreferencesKey(tKey)] = tValue
+ is String -> store[stringPreferencesKey(tKey)] = tValue
+ }
+ }
+ }
+ result.success(null)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataCorePlugin.kt b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataCorePlugin.kt
new file mode 100644
index 0000000..d230cef
--- /dev/null
+++ b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataCorePlugin.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package dev.patapata.patapata_core
+
+import android.util.Log
+import androidx.annotation.NonNull
+import androidx.annotation.Keep
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.embedding.engine.dart.DartExecutor
+import io.flutter.embedding.engine.loader.FlutterLoader
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+import java.io.File
+
+private class PatapataPluginContainer(val plugin: PatapataPlugin, val flutterPluginBinding: FlutterPlugin.FlutterPluginBinding)
+
+private val sPlugins = mutableSetOf()
+
+/** PatapataCorePlugin */
+class PatapataCorePlugin: FlutterPlugin, MethodCallHandler {
+ private lateinit var mChannel : MethodChannel
+ private lateinit var mBinding : FlutterPlugin.FlutterPluginBinding
+
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ mBinding = flutterPluginBinding
+ // Lines up with Flutter's override of Dart's Directory.systemTemp directory.
+ val tNativeLibDirectoryInfoFile = File(flutterPluginBinding.applicationContext.codeCacheDir, "patapataNativeLib")
+
+ if (tNativeLibDirectoryInfoFile.exists()) {
+ tNativeLibDirectoryInfoFile.delete()
+ }
+
+ tNativeLibDirectoryInfoFile.writeText(flutterPluginBinding.applicationContext.applicationInfo.nativeLibraryDir)
+
+ mChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "dev.patapata.patapata_core")
+ mChannel.setMethodCallHandler(this)
+
+ // Register default plugins.
+ flutterPluginBinding.registerPatapataPlugin(NativeLocalConfig(mBinding.applicationContext, mBinding.binaryMessenger))
+ }
+
+ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+ when (call.method) {
+ "enablePlugin" -> {
+ val tName = call.arguments as? String
+
+ if (tName == null) {
+ result.error(Error.PPE000.name, "Invalid plugin name passed to enablePlugin", null)
+
+ return
+ }
+
+ enablePlugin(tName)
+ result.success(null)
+ }
+ "disablePlugin" -> {
+ val tName = call.arguments as? String
+
+ if (tName == null) {
+ result.error(Error.PPE000.name, "Invalid plugin name passed to disablePlugin", null)
+
+ return
+ }
+
+ disablePlugin(tName)
+ result.success(null)
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ mChannel.setMethodCallHandler(null)
+ }
+
+ private fun enablePlugin(pluginName: String) {
+ sPlugins.firstOrNull {
+ it.plugin.patapataName == pluginName && it.flutterPluginBinding.binaryMessenger == mBinding.binaryMessenger
+ }?.plugin?.patapataEnable()
+ }
+
+ private fun disablePlugin(pluginName: String) {
+ sPlugins.firstOrNull {
+ it.plugin.patapataName == pluginName && it.flutterPluginBinding.binaryMessenger == mBinding.binaryMessenger
+ }?.plugin?.patapataDisable()
+ }
+}
+
+fun FlutterPlugin.FlutterPluginBinding.registerPatapataPlugin(plugin: PatapataPlugin) {
+ sPlugins.removeAll { it.plugin == plugin }
+ sPlugins.add(PatapataPluginContainer(plugin, this))
+}
+
+fun FlutterPlugin.FlutterPluginBinding.unregisterPatapataPlugin(plugin: PatapataPlugin) {
+ sPlugins.removeAll { it.plugin == plugin }
+}
diff --git a/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataPlugin.kt b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataPlugin.kt
new file mode 100644
index 0000000..2961f24
--- /dev/null
+++ b/packages/patapata_core/android/src/main/kotlin/dev/patapata/patapata_core/PatapataPlugin.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package dev.patapata.patapata_core
+
+interface PatapataPlugin {
+ val patapataName: String
+ fun patapataEnable() {}
+ fun patapataDisable() {}
+}
diff --git a/packages/patapata_core/bin/bootstrap.dart b/packages/patapata_core/bin/bootstrap.dart
new file mode 100644
index 0000000..a474147
--- /dev/null
+++ b/packages/patapata_core/bin/bootstrap.dart
@@ -0,0 +1,999 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:dart_style/dart_style.dart';
+import 'package:xml/xml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+void main(List arguments) {
+ late final ArgParser tParser;
+
+ tParser = ArgParser()
+ ..addSeparator(
+ 'Bootstrap your Patapata project.',
+ )
+ ..addFlag(
+ 'help',
+ abbr: 'h',
+ help: 'Show this help message.',
+ negatable: false,
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'force',
+ abbr: 'f',
+ help: 'Rewrite existing files.',
+ negatable: false,
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'i18n',
+ help: 'Enable I18n support.',
+ defaultsTo: true,
+ )
+ ..addMultiOption(
+ 'locale',
+ abbr: 'l',
+ help: 'Which locale codes to support.',
+ defaultsTo: ['en'],
+ )
+ ..addFlag(
+ 'errors',
+ help: 'Enable customization of the error handling system.',
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'log',
+ help: 'Enable customization of the logging system.',
+ defaultsTo: true,
+ )
+ ..addFlag(
+ 'notifications',
+ help: 'Enable customization of the notifications system.',
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'screenlayout',
+ help: 'Enable customization of ScreenLayout breakpoints.',
+ defaultsTo: false,
+ );
+
+ try {
+ final tResults = tParser.parse(arguments);
+
+ if (tResults['help'] == true) {
+ stdout.writeln(tParser.usage);
+ return;
+ }
+
+ if (tResults.rest.isNotEmpty) {
+ throw UsageException(
+ 'Unexpected arguments: ${tResults.rest.join(', ')}',
+ tParser.usage,
+ );
+ }
+
+ // Check if an environment.dart file exists.
+ // And if not, create it with a default Environment class.
+ _checkEnvironmentFile(tResults);
+
+ // Check if a splash_page.dart file exists.
+ // And if not, create it with a default SplashPage class.
+ _checkSplashPageFile(tResults);
+
+ // Check if a agreement_page.dart file exists.
+ // And if not, create it with a default AgreementPage class.
+ _checkAgreementPageFile(tResults);
+
+ // Check if a home_page.dart file exists.
+ // And if not, create it with a default HomePage class.
+ _checkHomePageFile(tResults);
+
+ // Check if a startup.dart file exists.
+ // And if not, create it with several default startup sequence states.
+ _checkStartupFile(tResults);
+
+ // Check if a errors.dart file exists.
+ // And if not, create it with some default errors.
+ _checkErrorsFile(tResults);
+
+ // Check if a main.dart file exists.
+ // And if not, create it with a default main function.
+ _checkMainFile(tResults);
+
+ // Check if an AndroidManifest.xml file exists.
+ // And if so, check if it contains the flutter_deeplinking_enabled meta-data.
+ _checkAndroidManifestFile(tResults);
+
+ // Check if the minimum version of the Android SDK is set to the flutter default,
+ // And if it is, change it to 21.
+ _checkAndroidGradleFile(tResults);
+
+ // Check if Info.plist file contains the FlutterDeepLinkingEnabled key.
+ // And if not, set it to true.
+ _checkInfoPlistFile(tResults);
+
+ // Check if the minumum version of iOS is unset.
+ // If it is, set it to 12.0.
+ _checkPodFile(tResults);
+
+ // Check if the l10n directory exists.
+ // And if not, create it.
+ _checkL10nFiles(tResults);
+ } catch (e) {
+ switch (e) {
+ case UsageException():
+ stderr.writeln(e.message);
+ stderr.writeln(e.usage);
+ exit(64);
+ case FormatException():
+ stderr.writeln(e.message);
+ stderr.writeln(tParser.usage);
+ exit(64);
+ default:
+ rethrow;
+ }
+ }
+}
+
+void _checkEnvironmentFile(ArgResults results) {
+ stdout.writeln('Checking environment.dart file...');
+
+ final tFile = File('lib/src/environment.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating environment.dart file...');
+ final tMixins = {
+ if (results['i18n'] == true) 'I18nEnvironment',
+ if (results['errors'] == true) 'ErrorEnvironment',
+ if (results['log'] == true) 'LogEnvironment',
+ if (results['notifications'] == true) 'NotificationsEnvironment',
+ if (results['screenlayout'] == true) 'ScreenLayoutEnvironment',
+ };
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/foundation.dart';
+import 'package:patapata_core/patapata_core.dart';
+${tMixins.contains('ScreenLayoutEnvironment') ? '''
+import 'package:patapata_core/patapata_widgets.dart';
+''' : ''}
+${tMixins.contains('ScreenLayoutEnvironment') || tMixins.contains('LogEnvironment') ? '''
+import 'package:flutter/widgets.dart';
+''' : ''}
+
+/// The Environment class for this app.
+/// Controls all static settings for the app.
+/// Pass this to the [App] constructor.
+class Environment${tMixins.isNotEmpty ? ' with\n${tMixins.join(',\n')}' : ''} {
+
+ ${tMixins.contains('I18nEnvironment') ? '''
+ @override
+ final List supportedL10ns = const [
+${[
+ for (final tLocale in results['locale'] as List)
+ "Locale('$tLocale')",
+ ].join(',\n')}
+ ];
+
+ @override
+ final List l10nPaths = const [
+ 'l10n',
+ ];
+
+''' : ''}
+ ${tMixins.contains('ErrorEnvironment') ? '''
+ @override
+ final Map? errorReplacePrefixMap;
+
+ @override
+ final Widget Function(PatapataException)? errorDefaultWidget;
+
+ @override
+ final Future Function(BuildContext, PatapataException)? errorDefaultShowDialog;
+
+''' : ''}
+${tMixins.contains('LogEnvironment') ? '''
+ @override
+ final int logLevel;
+
+ @override
+ final bool printLog;
+
+''' : ''}
+${tMixins.contains('NotificationsEnvironment') ? '''
+ @override
+ final String notificationsAndroidDefaultIcon;
+
+ @override
+ final bool notificationsDarwinDefaultPresentAlert;
+
+ @override
+ final bool notificationsDarwinDefaultPresentSound;
+
+ @override
+ final bool notificationsDarwinDefaultPresentBadge;
+
+ @override
+ final bool notificationsDarwinDefaultPresentBanner;
+
+ @override
+ final bool notificationsDarwinDefaultPresentList;
+
+ @override
+ final List notificationsAndroidChannels;
+
+ @override
+ final String notificationsPayloadLocationKey;
+
+''' : ''}
+${tMixins.contains('ScreenLayoutEnvironment') ? '''
+ @override
+ final Map screenLayoutBreakpoints;
+
+''' : ''}
+
+ const Environment({
+ ${tMixins.contains('ErrorEnvironment') ? '''
+ this.errorReplacePrefixMap,
+ this.errorDefaultWidget,
+ this.errorDefaultShowDialog,
+''' : ''}
+ ${tMixins.contains('LogEnvironment') ? '''
+ this.logLevel = const int.fromEnvironment('LOG_LEVEL', defaultValue: -kPataInHex),
+ this.printLog = const bool.fromEnvironment('PRINT_LOG', defaultValue: kDebugMode),
+''' : ''}
+ ${tMixins.contains('NotificationsEnvironment') ? '''
+ this.notificationsAndroidDefaultIcon = '@mipmap/ic_launcher',
+ this.notificationsDarwinDefaultPresentAlert = true,
+ this.notificationsDarwinDefaultPresentSound = true,
+ this.notificationsDarwinDefaultPresentBadge = true,
+ this.notificationsDarwinDefaultPresentBanner = true,
+ this.notificationsDarwinDefaultPresentList = true,
+ this.notificationsAndroidChannels = const [
+ NotificationsPlugin.kDefaultAndroidChannel,
+ ],
+ this.notificationsPayloadLocationKey = 'location',
+''' : ''}
+ ${tMixins.contains('ScreenLayoutEnvironment') ? '''
+ this.screenLayoutBreakpoints = const {
+ 'normal': ScreenLayoutDefaultBreakpoints.normal,
+ 'large': ScreenLayoutDefaultBreakpoints.large,
+ },
+''' : ''}
+ });
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkMainFile(ArgResults results) {
+ stdout.writeln('Checking main.dart file...');
+
+ final tFile = File('lib/main.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating main.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+import 'src/environment.dart';
+import 'src/startup.dart';
+import 'src/pages/error.dart';
+import 'src/pages/splash_page.dart';
+import 'src/pages/agreement_page.dart';
+import 'src/pages/home_page.dart';
+
+final _providerKey = GlobalKey(debugLabel: 'AppProviderKey');
+
+void main() {
+ App(
+ environment: const Environment(),
+ startupSequence: StartupSequence(
+ startupStateFactories: [
+ StartupStateFactory(
+ (startupSequence) => StartupStateCheckVersion(startupSequence),
+ [
+ LogicStateTransition(),
+ ],
+ ),
+ StartupStateFactory(
+ (startupSequence) => StartupStateAgreements(startupSequence),
+ [],
+ ),
+ ],
+ ),
+ createAppWidget: (context, app) => StandardMaterialApp(
+ onGenerateTitle: (context) => l(context, 'title'),
+ pages: [
+ // Splash screen page.
+ // This uses a special factory that has good defaults for splash screens.
+ SplashPageFactory(
+ create: (_) => SplashPage(),
+ ),
+ // Agreement page.
+ // This uses a special factory that all [StartupSequence] pages should use.
+ StartupPageFactory(
+ create: (_) => AgreementPage(),
+ ),
+ // Error page.
+ // This uses a special factory that all full screen error pages should use.
+ StandardErrorPageFactory(
+ create: (_) => ErrorPage(),
+ ),
+ StandardPageFactory(
+ create: (_) => HomePage(),
+ links: {
+ r'': (match, uri) {},
+ },
+ linkGenerator: (pageData) => '',
+ groupRoot: true,
+ ),
+ ],
+ routableBuilder: (context, child) {
+ // Setup [ScreenLayout]
+ // You may want to move this to the body section of your Scaffold
+ // or somewhere where it makes sense for your app's design.
+ child = ScreenLayout(child: child);
+
+ // Wrap the app in a key provided by you
+ // so you can access your providers from anywhere
+ // via context.read and context.watch.
+ child = KeyedSubtree(
+ key: _providerKey,
+ child: child,
+ );
+
+ // If you want to customize a Theme, you can do it here
+ // by wrapping the child with a Theme widget.
+ // You can also wrap anything here and that Widget will
+ // be available to all pages.
+
+ // Add your [Provider]s here
+ // child = MultiProvider(
+ // providers: const [
+ // // Provider(
+ // // create: (_) => YourProvider(),
+ // // ),
+ // ],
+ // child: child,
+ // );
+
+ return child;
+ },
+ ),
+ plugins: [],
+ providerKey: _providerKey,
+ )
+ ..getPlugin()?.enableStandardAppIntegration()
+ ..run(() async {
+ // Do any initialization here
+ // Here's a good default
+
+ // Set a default orientation of only portrait
+ await SystemChrome.setPreferredOrientations(const [
+ DeviceOrientation.portraitDown,
+ DeviceOrientation.portraitUp,
+ ]);
+
+ // Enable Edge-to-Edge mode
+ await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
+
+ // Make the status bars transparent
+ SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
+ systemNavigationBarColor: Colors.transparent,
+ statusBarColor: Colors.transparent,
+ statusBarBrightness: Brightness.light,
+ statusBarIconBrightness: Brightness.dark,
+ ));
+
+ // Set your RemoteConfig defaults here
+ await getApp().remoteConfig.setDefaults(const {});
+ });
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkSplashPageFile(ArgResults results) {
+ stdout.writeln('Checking splash_page.dart file...');
+
+ final tFile = File('lib/src/pages/splash_page.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating splash_page.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/material.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+class SplashPage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ return const Center(
+ child: FlutterLogo(
+ size: 128,
+ ),
+ );
+ }
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkAgreementPageFile(ArgResults results) {
+ stdout.writeln('Checking agreement_page.dart file...');
+
+ final tFile = File('lib/src/pages/agreement_page.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating agreement_page.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/material.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+class AgreementPage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'pages.agreement.title')),
+ ),
+ body: Column(
+ children: [
+ Center(
+ child: Text(
+ l(context, 'pages.agreement.body'),
+ ),
+ ),
+ TextButton(
+ child: Text(l(context, 'pages.agreement.yes')),
+ onPressed: () {
+ pageData(null);
+ },
+ ),
+ TextButton(
+ child: Text(l(context, 'pages.agreement.no')),
+ onPressed: () {
+ getApp().startupSequence?.resetMachine();
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkHomePageFile(ArgResults results) {
+ stdout.writeln('Checking home_page.dart file...');
+
+ final tFile = File('lib/src/pages/home_page.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating home_page.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/material.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+class HomePage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'pages.home.title')),
+ ),
+ body: Center(
+ child: Text(l(context, 'pages.home.body')),
+ ),
+ );
+ }
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkStartupFile(ArgResults results) {
+ stdout.writeln('Checking startup.dart file...');
+
+ final tFile = File('lib/src/startup.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating startup.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:patapata_core/patapata_core.dart';
+
+import 'errors.dart';
+import 'pages/agreement_page.dart';
+
+class StartupStateCheckVersion extends StartupState {
+ StartupStateCheckVersion(StartupSequence startupSequence) : super(startupSequence);
+
+ @override
+ Future process(Object? data) async {
+ // Check the version here
+ // If the version is not supported, throw an exception
+ // and the app will show the error page.
+ // If the version is supported, transition to the next state.
+ final tIsNewestVersion = true; // TODO: Change this with your own logic.
+
+ if (!tIsNewestVersion) {
+ throw const AppVersionException();
+ }
+ }
+}
+
+class StartupStateAgreements extends StartupState {
+ /// The version of the agreement.
+ /// This should be incremented when the agreement changes.
+ static const kVersion = '1';
+
+ static const _kAgreementVersionKey = 'agreementVersion';
+
+ StartupStateAgreements(StartupSequence startupSequence) : super(startupSequence);
+
+ @override
+ Future process(Object? data) async {
+ // Check if the user has agreed to the agreement.
+ // If the user has agreed, return.
+ if (getApp().localConfig.getString(_kAgreementVersionKey) == kVersion) {
+ return;
+ }
+
+ // Show the agreement page here.
+ // If the user agrees, call pageData(null);
+ // If the user does not agree, call getApp().startupSequence?.resetMachine();
+ // which will reset the startup sequence.
+ if (await navigateToPage(AgreementPage, (result) {})) {
+ await getApp().localConfig.setString(_kAgreementVersionKey, kVersion);
+ }
+ }
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkErrorsFile(ArgResults results) {
+ stdout.writeln('Checking errors.dart file...');
+
+ final tFile = File('lib/src/errors.dart');
+ final tFileExists = tFile.existsSync();
+
+ if (results['force'] == true || !tFileExists) {
+ stdout.writeln('Creating errors.dart file...');
+
+ if (!tFileExists) {
+ tFile.createSync(recursive: true);
+ }
+
+ tFile.writeAsStringSync(DartFormatter().format('''
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_core_libs.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+abstract base class AppException extends PatapataException {
+ const AppException({
+ super.app,
+ super.message,
+ super.original,
+ super.fingerprint,
+ super.localeTitleData,
+ super.localeMessageData,
+ super.localeFixData,
+ super.fix,
+ super.logLevel,
+ super.userLogLevel,
+ });
+
+ @override
+ // TODO: This should be a 3 letter code that is unique to your app.
+ // TODO: We recommend using a mapping like the first two letters being
+ // TODO: related to your app, and the last letter being related to the
+ // TODO: type of error. E for error, W for warning, etc.
+ String get defaultPrefix => 'APE';
+
+ @override
+ String get namespace => 'app';
+}
+
+/// An exception that is thrown when the app encounters an unknown error.
+final class AppUnknownException extends AppException {
+ const AppUnknownException();
+
+ @override
+ String get internalCode => '000';
+}
+
+/// Thrown when an unsupported version (usually old) of the app is detected.
+final class AppVersionException extends AppException {
+ const AppVersionException() : super(logLevel: Level.INFO);
+
+ @override
+ String get internalCode => '010';
+
+ @override
+ void onReported(ReportRecord record) {
+ showDialog(getApp().navigatorContext);
+ }
+
+ @override
+ Future Function()? get fix => () async {
+ // Launch the app store.
+ };
+}
+'''));
+ }
+
+ // Also check and create the ErrorPage.
+ final tErrorPageFile = File('lib/src/pages/error.dart');
+ final tErrorPageFileExists = tErrorPageFile.existsSync();
+
+ if (results['force'] == true || !tErrorPageFileExists) {
+ stdout.writeln('Creating error.dart file...');
+
+ if (!tErrorPageFileExists) {
+ tErrorPageFile.createSync(recursive: true);
+ }
+
+ tErrorPageFile.writeAsStringSync(DartFormatter().format('''
+import 'package:flutter/material.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+
+import '../errors.dart';
+
+class ErrorPage extends StandardPage {
+ @override
+ Widget buildPage(BuildContext context) {
+ if (pageData.error is AppException) {
+ return _buildAppExceptionPage(context);
+ } else {
+ return _buildUnknownExceptionPage(context);
+ }
+ }
+
+ Widget _buildAppExceptionPage(BuildContext context) {
+ final tAppException = pageData.error as AppException;
+
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(tAppException.localizedTitle),
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ children: [
+ Text(tAppException.localizedMessage),
+ if (tAppException.hasFix)
+ TextButton(
+ child: Text(tAppException.localizedFix),
+ onPressed: () {
+ tAppException.fix!();
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildUnknownExceptionPage(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(l(context, 'errors.app.000.title')),
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ children: [
+ Text(l(context, 'errors.app.000.message')),
+ ],
+ ),
+ ),
+ );
+ }
+}
+'''));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkAndroidManifestFile(ArgResults results) {
+ stdout.writeln('Checking AndroidManifest.xml file...');
+
+ // Check if the AndroidManifest.xml file contains an main activity that has
+ // meta-data of flutter_deeplinking_enabled, and if not, add it.
+ final tFile = File('android/app/src/main/AndroidManifest.xml');
+ final tFileExists = tFile.existsSync();
+
+ if (!tFileExists) {
+ stdout.writeln('AndroidManifest.xml file not found.');
+
+ return;
+ }
+
+ final tDocument = XmlDocument.parse(tFile.readAsStringSync());
+ final tMainActivities =
+ tDocument.findAllElements('activity').where((element) {
+ final tIntentFilters =
+ element.findAllElements('intent-filter').where((element) {
+ final tActions = element.findAllElements('action').where((element) {
+ return element.getAttribute('android:name') ==
+ 'android.intent.action.MAIN';
+ });
+
+ final tCategories = element.findAllElements('category').where((element) {
+ return element.getAttribute('android:name') ==
+ 'android.intent.category.LAUNCHER';
+ });
+
+ return tActions.isNotEmpty && tCategories.isNotEmpty;
+ });
+
+ return tIntentFilters.isNotEmpty;
+ });
+
+ if (tMainActivities.isEmpty) {
+ stdout.writeln('No main activity found.');
+
+ return;
+ }
+
+ final tMainActivity = tMainActivities.first;
+
+ final tMetaData = tMainActivity.findAllElements('meta-data').where((element) {
+ return element.getAttribute('android:name') ==
+ 'flutter_deeplinking_enabled';
+ });
+
+ if (tMetaData.isEmpty) {
+ stdout.writeln(
+ 'No meta-data found. Adding the Flutter Deep Linking feature.');
+
+ tMainActivity.children.add(XmlElement(
+ XmlName('meta-data'),
+ [
+ XmlAttribute(XmlName('android:name'), 'flutter_deeplinking_enabled'),
+ XmlAttribute(XmlName('android:value'), 'true'),
+ ],
+ [],
+ false,
+ ));
+
+ tFile.writeAsStringSync(tDocument.toXmlString(pretty: true));
+ }
+}
+
+void _checkAndroidGradleFile(ArgResults results) {
+ stdout.writeln('Checking Android Gradle file...');
+
+ // Check if the Android Gradle file contains the minimum SDK version of 21.
+ // And if not, change it to 21.
+ final tFile = File('android/app/build.gradle');
+ final tFileExists = tFile.existsSync();
+
+ if (!tFileExists) {
+ stdout.writeln('Android Gradle file not found.');
+
+ return;
+ }
+
+ // Replace the minimum SDK version with 21 as plain text
+ final tDocument = tFile
+ .readAsStringSync()
+ .replaceAll('minSdkVersion flutter.minSdkVersion', 'minSdkVersion 21');
+
+ tFile.writeAsStringSync(tDocument.toString());
+}
+
+void _checkInfoPlistFile(ArgResults results) {
+ stdout.writeln('Checking Info.plist file...');
+
+ // Check if the Info.plist file contains the FlutterDeepLinkingEnabled key.
+ // And if not, set it to true.
+ final tFile = File('ios/Runner/Info.plist');
+ final tFileExists = tFile.existsSync();
+
+ if (!tFileExists) {
+ stdout.writeln('Info.plist file not found.');
+
+ return;
+ }
+
+ final tDocument = XmlDocument.parse(tFile.readAsStringSync());
+ // This should be the first element under plist.
+ final tDictElement = tDocument.findAllElements('dict').first;
+ final tKeys = tDictElement.findAllElements('key').where((element) {
+ return element.innerText == 'FlutterDeepLinkingEnabled';
+ });
+
+ if (tKeys.isEmpty) {
+ stdout.writeln(
+ 'No FlutterDeepLinkingEnabled key found. Adding the Flutter Deep Linking feature.');
+
+ tDictElement.children.add(XmlElement(
+ XmlName('key'),
+ [],
+ [
+ XmlText('FlutterDeepLinkingEnabled'),
+ ],
+ false,
+ ));
+
+ tDictElement.children.add(XmlElement(
+ XmlName('true'),
+ [],
+ [],
+ true,
+ ));
+
+ tFile.writeAsStringSync(tDocument.toXmlString(pretty: true));
+ }
+
+ stdout.writeln('Done.');
+}
+
+void _checkPodFile(ArgResults results) {
+ stdout.writeln('Checking Podfile file...');
+
+ // Check if the Podfile file contains the minimum version of iOS of 12.0.
+ // And if not, change it to 12.0.
+ final tFile = File('ios/Podfile');
+ final tFileExists = tFile.existsSync();
+
+ if (!tFileExists) {
+ stdout.writeln('Podfile file not found.');
+
+ return;
+ }
+
+ // Replace the minimum version of iOS with 12.0 as plain text
+ // The default setting after flutter create is a commented out line.
+ // So we remove that whole line and replace it.
+ // As of writting this code, the default line is:
+ // # platform :ios, '11.0'
+ final tDocument = tFile
+ .readAsStringSync()
+ .replaceAll('# platform :ios, \'11.0\'', 'platform :ios, \'12.0\'');
+
+ tFile.writeAsStringSync(tDocument.toString());
+}
+
+void _checkL10nFiles(ArgResults results) {
+ stdout.writeln('Checking l10n files...');
+
+ // Check if the l10n directory exists.
+ // And if not, create it.
+ final tDirectory = Directory('l10n');
+ final tDirectoryExists = tDirectory.existsSync();
+
+ if (!tDirectoryExists) {
+ stdout.writeln('l10n directory not found. Creating it.');
+ tDirectory.createSync(recursive: true);
+ }
+
+ // Check if each of the Locales specified in the arguments exists.
+ // If not, create them.
+ for (final tLocale in results['locale'] as List) {
+ final tFile = File('l10n/$tLocale.yaml');
+ final tFileExists = tFile.existsSync();
+
+ if (!tFileExists) {
+ stdout.writeln('l10n/$tLocale.yaml file not found. Creating it.');
+ tFile.createSync(recursive: true);
+
+ tFile.writeAsStringSync('''
+title: App Title
+pages:
+ agreement:
+ title: Agreement
+ body: This is the agreement page. Do you accept?
+ yes: Yes
+ no: No
+ home:
+ title: Home
+ body: This is the home page.
+errors:
+ app:
+ '000':
+ title: Unknown Error
+ message: An unknown error has occurred.
+ '010':
+ title: Unsupported Version
+ message: This version of the app is no longer supported.
+ fix: Please update the app.
+''');
+ }
+ }
+
+ stdout.writeln('Adding l10n.yaml files to flutter assets in pubspec.yaml...');
+
+ final tPubspecFile = File('pubspec.yaml');
+ final tPubspecFileExists = tPubspecFile.existsSync();
+
+ if (!tPubspecFileExists) {
+ stdout.writeln('pubspec.yaml file not found.');
+
+ return;
+ }
+
+ final tPubspecDocument = YamlEditor(tPubspecFile.readAsStringSync());
+
+ tPubspecDocument.parseAt(
+ ['flutter', 'assets'],
+ orElse: () {
+ tPubspecDocument.update([
+ 'flutter'
+ ], {
+ 'assets': [],
+ });
+
+ return tPubspecDocument.parseAt(['flutter', 'assets']);
+ },
+ );
+
+ for (final tLocale in results['locale'] as List) {
+ tPubspecDocument.appendToList(['flutter', 'assets'], 'l10n/$tLocale.yaml');
+ }
+
+ tPubspecFile.writeAsStringSync(tPubspecDocument.toString());
+
+ stdout.writeln('Done.');
+}
diff --git a/packages/patapata_core/dartdoc_options.yaml b/packages/patapata_core/dartdoc_options.yaml
new file mode 100644
index 0000000..ad58258
--- /dev/null
+++ b/packages/patapata_core/dartdoc_options.yaml
@@ -0,0 +1,15 @@
+# Copyright (c) GREE, Inc.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+dartdoc:
+ exclude:
+ - patapata_core_libs
+ - local_config_finder
+ - patapata_core_web
+ - patapata_web_plugin
+ - web_local_config
+ - web_local_config_finder
+ - native_ffi_finder
+ - native_local_config
diff --git a/packages/patapata_core/ios/.gitignore b/packages/patapata_core/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/packages/patapata_core/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/packages/patapata_core/ios/Assets/.gitkeep b/packages/patapata_core/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/packages/patapata_core/ios/Classes/NativeLocalConfig.swift b/packages/patapata_core/ios/Classes/NativeLocalConfig.swift
new file mode 100644
index 0000000..bce9bda
--- /dev/null
+++ b/packages/patapata_core/ios/Classes/NativeLocalConfig.swift
@@ -0,0 +1,128 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Flutter
+
+class NativeLocalConfig : PatapataPlugin {
+ fileprivate let mChannel: FlutterMethodChannel
+ fileprivate var mOnChangeListener: Any?
+ fileprivate var mOnSizeLimitExceededListener: Any?
+ fileprivate let mStore = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier ?? "unknown").dev.patapata.native_local_config") ?? UserDefaults.standard
+
+ init(registrar: FlutterPluginRegistrar) {
+ mChannel = FlutterMethodChannel(name: "dev.patapata.native_local_config", binaryMessenger: registrar.messenger())
+ }
+
+ public var patapataName: String = "dev.patapata.native_local_config"
+
+ public func patapataEnable() {
+ mChannel.setMethodCallHandler(handle)
+
+ mOnChangeListener = NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: mStore, queue: OperationQueue.main, using: onChange)
+
+ if #available(iOS 9.3, *) {
+ mOnSizeLimitExceededListener = NotificationCenter.default.addObserver(forName: UserDefaults.sizeLimitExceededNotification, object: mStore, queue: OperationQueue.main, using: onSizeLimitExceeded)
+ }
+
+ syncStore()
+ }
+
+ fileprivate func syncStore() {
+ let tDict = mStore.dictionaryRepresentation().filter {
+ switch $0.value {
+ case is Bool:
+ return true
+ case is Int:
+ return true
+ case is Double:
+ return true
+ case is String:
+ return true
+ default:
+ return false
+ }
+ }
+
+ mChannel.invokeMethod("syncAll", arguments: tDict)
+ }
+
+ fileprivate func onChange(_: Notification) {
+ syncStore()
+ }
+
+ fileprivate func onSizeLimitExceeded(notification: Notification) {
+ // mChannel.invokeMethod("error", arguments: <#T##Any?#>)
+ // Should we send this? It could happen async from a set command
+ // It could also happen from something not related to patapata at all...
+ }
+
+ public func patapataDisable() {
+ mChannel.setMethodCallHandler(nil)
+ NotificationCenter.default.removeObserver(mOnChangeListener!, name: UserDefaults.didChangeNotification, object: mStore)
+ mOnChangeListener = nil
+
+ if #available(iOS 9.3, *) {
+ NotificationCenter.default.removeObserver(mOnSizeLimitExceededListener!, name: UserDefaults.sizeLimitExceededNotification, object: mStore)
+ mOnSizeLimitExceededListener = nil
+ }
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "reset":
+ mStore.removeObject(forKey: call.arguments as! String)
+ result(nil)
+ break
+ case "resetMany":
+ let tArgs = call.arguments as! Array
+
+ for i in tArgs {
+ mStore.removeObject(forKey: i)
+ }
+
+ result(nil)
+ break
+ case "resetAll":
+ for i in mStore.dictionaryRepresentation() {
+ mStore.removeObject(forKey: i.key)
+ }
+
+ result(nil)
+ break
+ case "setBool":
+ let tArgs = call.arguments as! Array
+ mStore.set(tArgs[1] as! Bool, forKey: tArgs[0] as! String)
+ result(nil)
+ break
+ case "setInt":
+ let tArgs = call.arguments as! Array
+ mStore.set(tArgs[1] as! Int, forKey: tArgs[0] as! String)
+ result(nil)
+ break
+ case "setDouble":
+ let tArgs = call.arguments as! Array
+ mStore.set(tArgs[1] as! Double, forKey: tArgs[0] as! String)
+ result(nil)
+ break
+ case "setString":
+ let tArgs = call.arguments as! Array
+ mStore.set(tArgs[1] as! String, forKey: tArgs[0] as! String)
+ result(nil)
+ break
+ case "setMany":
+ let tArgs = call.arguments as! Dictionary
+
+ for i in tArgs {
+ mStore.set(i.value, forKey: i.key)
+ }
+
+ result(nil)
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+}
diff --git a/packages/patapata_core/ios/Classes/PatapataCorePlugin.swift b/packages/patapata_core/ios/Classes/PatapataCorePlugin.swift
new file mode 100644
index 0000000..d36dc26
--- /dev/null
+++ b/packages/patapata_core/ios/Classes/PatapataCorePlugin.swift
@@ -0,0 +1,128 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import Flutter
+import UIKit
+import AppTrackingTransparency
+
+fileprivate var sPlugins = Set()
+
+public class PatapataCorePlugin: NSObject, FlutterPlugin {
+ let mRegistrar: FlutterPluginRegistrar
+ let mChannel: FlutterMethodChannel
+
+ init(registrar: FlutterPluginRegistrar) {
+ mRegistrar = registrar
+ mChannel = FlutterMethodChannel(name: "dev.patapata.patapata_core", binaryMessenger: registrar.messenger())
+
+ super.init()
+
+ registrar.addMethodCallDelegate(self, channel: mChannel)
+
+ // Register default plugins.
+ registrar.registerPatapata(plugin: NativeLocalConfig(registrar: registrar))
+ }
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let _ = PatapataCorePlugin(registrar: registrar)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "enablePlugin":
+ guard let tName = call.arguments as? String else {
+ result(FlutterError(code: "PPE000", message: "Invalid plugin name passed to enablePlugin", details: nil))
+
+ return
+ }
+
+ enablePlugin(with: tName)
+ result(nil)
+ case "disablePlugin":
+ guard let tName = call.arguments as? String else {
+ result(FlutterError(code: "PPE000", message: "Invalid plugin name passed to disablePlugin", details: nil))
+
+ return
+ }
+
+ disablePlugin(with: tName)
+ result(nil)
+ case "Permissions:requestTracking":
+ requestTrackingPermission(result)
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+
+ public func enablePlugin(with pluginName: String) {
+ for i in sPlugins {
+ guard i.plugin.patapataName == pluginName && i.registrar.messenger().hash == mRegistrar.messenger().hash else {
+ continue
+ }
+
+ i.plugin.patapataEnable();
+ }
+ }
+
+ public func disablePlugin(with pluginName: String) {
+ for i in sPlugins {
+ guard i.plugin.patapataName == pluginName && i.registrar.messenger().hash == mRegistrar.messenger().hash else {
+ continue
+ }
+
+ i.plugin.patapataDisable()
+ }
+ }
+
+ func requestTrackingPermission(_ result: @escaping FlutterResult) {
+ if #available(iOS 14, *) {
+ ATTrackingManager.requestTrackingAuthorization { (status) in
+ switch status {
+ case .authorized:
+ result("authorized")
+ break
+ case .denied:
+ result("denied")
+ break
+ case .notDetermined:
+ result("notDetermined")
+ break
+ case .restricted:
+ result("restricted")
+ break
+ default:
+ result(nil)
+ }
+ }
+ } else {
+ result(nil)
+ }
+ }
+}
+
+fileprivate struct PatapataPluginContainer : Hashable {
+ let plugin: PatapataPlugin
+ let registrar: FlutterPluginRegistrar
+
+ static func == (lhs: PatapataPluginContainer, rhs: PatapataPluginContainer) -> Bool {
+ return lhs.plugin.patapataName == rhs.plugin.patapataName && lhs.registrar.messenger().hash == rhs.registrar.messenger().hash
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(plugin.patapataName)
+ hasher.combine(registrar.messenger().hash)
+ }
+}
+
+
+extension FlutterPluginRegistrar {
+ public func registerPatapata(plugin: PatapataPlugin) {
+ sPlugins.insert(PatapataPluginContainer(plugin: plugin, registrar: self))
+ }
+
+ public func unregisterPatapata(plugin: PatapataPlugin) {
+ sPlugins.remove(PatapataPluginContainer(plugin: plugin, registrar: self))
+ }
+}
diff --git a/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.h b/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.h
new file mode 100644
index 0000000..c068d98
--- /dev/null
+++ b/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) GREE, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import
+
+@interface PatapataCorePluginBridge : NSObject
+@end
diff --git a/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.m b/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.m
new file mode 100644
index 0000000..951eb16
--- /dev/null
+++ b/packages/patapata_core/ios/Classes/PatapataCorePluginBridge.m
@@ -0,0 +1,20 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+#import "PatapataCorePluginBridge.h"
+#if __has_include()
+#import
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "patapata_core-Swift.h"
+#endif
+
+@implementation PatapataCorePluginBridge
++ (void)registerWithRegistrar:(NSObject*)registrar {
+ [PatapataCorePlugin registerWithRegistrar:registrar];
+}
+@end
diff --git a/packages/patapata_core/ios/Classes/PatapataPlugin.swift b/packages/patapata_core/ios/Classes/PatapataPlugin.swift
new file mode 100644
index 0000000..c9ca8f6
--- /dev/null
+++ b/packages/patapata_core/ios/Classes/PatapataPlugin.swift
@@ -0,0 +1,16 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+public protocol PatapataPlugin {
+ var patapataName: String { get }
+ func patapataEnable()
+ func patapataDisable()
+}
+
+public extension PatapataPlugin {
+ func patapataEnable() {}
+ func patapataDisable() {}
+}
diff --git a/packages/patapata_core/ios/patapata_core.podspec b/packages/patapata_core/ios/patapata_core.podspec
new file mode 100644
index 0000000..0d98a49
--- /dev/null
+++ b/packages/patapata_core/ios/patapata_core.podspec
@@ -0,0 +1,23 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint patapata_core.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'patapata_core'
+ s.version = '0.0.1'
+ s.summary = 'A new flutter plugin project.'
+ s.description = <<-DESC
+A new flutter plugin project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '8.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+end
diff --git a/packages/patapata_core/lib/finder/local_config_finder.dart b/packages/patapata_core/lib/finder/local_config_finder.dart
new file mode 100644
index 0000000..b93a734
--- /dev/null
+++ b/packages/patapata_core/lib/finder/local_config_finder.dart
@@ -0,0 +1,9 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'package:patapata_core/patapata_core.dart';
+
+LocalConfigFinder getLocalConfigFinder() =>
+ throw UnsupportedError('Cannot create a LocalConfigFinder');
diff --git a/packages/patapata_core/lib/patapata_core.dart b/packages/patapata_core/lib/patapata_core.dart
new file mode 100644
index 0000000..4274735
--- /dev/null
+++ b/packages/patapata_core/lib/patapata_core.dart
@@ -0,0 +1,31 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+library patapata_core;
+
+export 'src/util.dart';
+export 'src/sequential_work_queue.dart';
+export 'src/logic_state.dart';
+export 'src/provider_model.dart';
+export 'src/config.dart';
+export 'src/remote_config.dart';
+export 'src/local_config.dart';
+export 'src/remote_messaging.dart';
+export 'src/log.dart';
+export 'src/analytics.dart';
+export 'src/user.dart';
+export 'src/network.dart';
+export 'src/package_info.dart';
+export 'src/device_info.dart';
+export 'src/plugin.dart';
+export 'src/app.dart';
+export 'src/i18n.dart';
+export 'src/permissions.dart';
+export 'src/notifications.dart';
+export 'src/error.dart';
+export 'src/startup.dart';
+
+export 'src/native_local_config.dart';
+export 'src/method_channel_test_mixin.dart';
diff --git a/packages/patapata_core/lib/patapata_core_libs.dart b/packages/patapata_core/lib/patapata_core_libs.dart
new file mode 100644
index 0000000..ab6cd19
--- /dev/null
+++ b/packages/patapata_core/lib/patapata_core_libs.dart
@@ -0,0 +1,11 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+library patapata_core_libs;
+
+export 'dart:async';
+
+export 'package:logging/logging.dart';
+export 'package:timezone/timezone.dart';
diff --git a/packages/patapata_core/lib/patapata_core_web.dart b/packages/patapata_core/lib/patapata_core_web.dart
new file mode 100644
index 0000000..047edd7
--- /dev/null
+++ b/packages/patapata_core/lib/patapata_core_web.dart
@@ -0,0 +1,67 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+import 'package:patapata_core/web/patapata_web_plugin.dart';
+import 'package:patapata_core/web/web_local_config.dart';
+
+var _sPlugins = {};
+
+class PatapataCoreWeb {
+ static void registerWith(Registrar registrar) {
+ final MethodChannel channel = MethodChannel(
+ 'dev.patapata.patapata_core',
+ const StandardMethodCodec(),
+ registrar,
+ );
+
+ final tPluginInstance = PatapataCoreWeb();
+ channel.setMethodCallHandler(tPluginInstance.handleMethodCall);
+
+ _sPlugins.add(WebLocalConfig(registrar));
+ }
+
+ Future handleMethodCall(MethodCall call) async {
+ switch (call.method) {
+ case 'enablePlugin':
+ var tName = call.arguments as String?;
+ if (tName == null) {
+ return;
+ }
+ return enablePlugin(tName);
+ case 'disablePlugin':
+ var tName = call.arguments as String?;
+ if (tName == null) {
+ return;
+ }
+ return disablePlugin(tName);
+ default:
+ throw PlatformException(
+ code: 'Unimplemented',
+ details:
+ 'patapata_core for web doesn\'t implement \'${call.method}\'',
+ );
+ }
+ }
+
+ void enablePlugin(String pluginName) {
+ for (var plugin in _sPlugins) {
+ if (plugin.patapataName == pluginName) {
+ plugin.patapataEnable();
+ return;
+ }
+ }
+ }
+
+ void disablePlugin(String pluginName) {
+ for (var plugin in _sPlugins) {
+ if (plugin.patapataName == pluginName) {
+ plugin.patapataDisable();
+ return;
+ }
+ }
+ }
+}
diff --git a/packages/patapata_core/lib/patapata_widgets.dart b/packages/patapata_core/lib/patapata_widgets.dart
new file mode 100644
index 0000000..01e8eb2
--- /dev/null
+++ b/packages/patapata_core/lib/patapata_widgets.dart
@@ -0,0 +1,10 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+library patapata_widgets;
+
+export 'src/widgets/standard_app.dart';
+export 'src/widgets/screen_layout.dart';
+export 'src/widgets/platform_dialog.dart';
diff --git a/packages/patapata_core/lib/src/analytics.dart b/packages/patapata_core/lib/src/analytics.dart
new file mode 100644
index 0000000..6f3ca13
--- /dev/null
+++ b/packages/patapata_core/lib/src/analytics.dart
@@ -0,0 +1,1291 @@
+// Copyright (c) GREE, Inc.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+import 'package:patapata_core/patapata_core.dart';
+import 'package:patapata_core/patapata_widgets.dart';
+import 'package:patapata_core/patapata_core_libs.dart';
+import 'package:provider/provider.dart';
+import 'package:provider/single_child_widget.dart';
+import 'package:visibility_detector/visibility_detector.dart';
+
+final _logger = Logger('patapata.Analytics');
+
+class _AnalyticsGlobalContextRouteKey {}
+
+/// A mixin for an [App.environment] that can filter [AnalyticsEvent]s.
+mixin AnalyticsEventFilterEnvironment {
+ /// A map of [Type] to a function that takes an [AnalyticsEvent] and returns
+ /// an [AnalyticsEvent] or null if the event should be filtered.
+ /// The [Type] is usually a [Plugin] type or another custom type.
+ /// You can use this to force certain events to only be sent to certain
+ /// plugins or other types as well as transform the event.
+ Map
+ get analyticsEventFilter;
+}
+
+/// An analytics class that notifies and monitors information related to events within the app,
+/// user tracking, and more, to external services or within the app itself.
+class Analytics {
+ final _eventStreamController = StreamController.broadcast();
+
+ /// The stream of [AnalyticsEvent] that are sent via [event], [rawEvent], and other method that send events.
+ Stream get events => _eventStreamController.stream;
+
+ /// Listen to analytics events.
+ /// These will be filtered by checking [T] in the
+ /// [App.environment] if it's a [AnalyticsEventFilterEnvironment]
+ /// to see if a given event should be sent to that type [T].
+ Stream eventsFor() {
+ return events
+ .map((event) {
+ final tEnvironment =
+ getApp().environmentAs();
+
+ if (tEnvironment?.analyticsEventFilter.containsKey(T) == true) {
+ return tEnvironment!.analyticsEventFilter[T]!(event);
+ }
+ return event;
+ })
+ .where((event) => event != null)
+ .map((event) => event!);
+ }
+
+ late final _globalContext = _MultiAnalyticsContext()
+ ..add(_globalContextRouteKey, _globalRouteContext);
+
+ /// Analytics Context found in the route.
+ AnalyticsContext get globalContext => _globalContext;
+
+ /// Adds an [AnalyticsContext] with the [key] name to the global context.
+ /// Passing null for [context] will remove it from the global context.
+ ///
+ /// example:
+ ///
+ /// ```dart
+ /// // If you want to add context information to all events
+ /// final _key = Object();
+ /// getApp().analytics.setGlobalContext(_key, AnalyticsContext(
+ /// data: {
+ /// 'key1': 1,
+ /// },
+ /// ));
+ /// // When you want to remove it
+ /// getApp().analytics.setGlobalContext(_key, null);
+ /// ```
+ void setGlobalContext(Object key, AnalyticsContext? context) {
+ _logger.fine('Changing global context: key: $key, context: $context');
+
+ if (context == null) {
+ _globalContext.remove(key);
+ } else {
+ _globalContext.add(key, context);
+ }
+ }
+
+ /// The [AnalyticsContext] that was set with [key] in the global context.
+ AnalyticsContext? getGlobalContext(Object key) => _globalContext.get(key);
+
+ final _globalContextRouteKey = _AnalyticsGlobalContextRouteKey();
+ final _globalRouteContext = _MultiAnalyticsContext();
+
+ /// Add an [AnalyticsContext] with the [key] name to the global route context.
+ /// If you pass null for the argument [context] of this function, it will be removed from the global route context.
+ /// The values set in this Route Context will disappear when transitioning to other pages.
+ /// example:
+ ///
+ /// ```dart
+ /// final _key = Object();
+ /// getApp().analytics.setRouteContext(_key, AnalyticsContext(
+ /// data: {
+ /// 'key1': 1,
+ /// },
+ /// ));
+ ///
+ /// // When deleting manually
+ /// getApp().analytics.setRouteContext(_key, null);
+ /// ```
+ void setRouteContext(Object key, AnalyticsContext? context) {
+ if (context == null) {
+ _globalRouteContext.remove(key);
+ } else {
+ _globalRouteContext.add(key, context);
+ }
+ }
+
+ /// The [AnalyticsContext] that was set with [key] in the global route context.
+ AnalyticsContext? getRouteContext(Object key) => _globalRouteContext.get(key);
+
+ AnalyticsContext? __interactionContext;
+ Map? _interactionContextData;
+
+ /// A Map of data from the Context of a Widget with the most recently interacted (touched) with Analytics information by the user.
+ Map? get interactionContextData => _interactionContextData;
+ set _interactionContext(AnalyticsContext? context) {
+ if (context != __interactionContext) {
+ _logger.fine('Changing interaction context: $context');
+ __interactionContext = context;
+ _interactionContextData = context?.resolve();
+ }
+ }
+
+ Map? _navigationInteractionContextData;
+
+ /// A Map of data to retain analytics information for page transitions.
+ Map? get navigationInteractionContextData =>
+ _navigationInteractionContextData;
+
+ void _promoteInteractionContextToNavigationContext() {
+ _logger.finer('Promoting interaction context...');
+ _navigationInteractionContextData = interactionContextData;
+ _interactionContext = null;
+ }
+
+ /// Send analytics for the information in [event].
+ /// Used when you want to send custom analytics events.
+ ///
+ /// example:
+ ///
+ /// ```dart
+ /// getApp().analytics.rawEvent(AnalyticsEvent(
+ /// name: 'Custom Hogehoge Event',
+ /// ));
+ /// ```
+ void rawEvent(
+ AnalyticsEvent event, {
+ Level logLevel = Level.INFO,
+ }) {
+ event._navigationInteractionContextData = navigationInteractionContextData;
+
+ _logger.log(logLevel, () => '$event');
+ _eventStreamController.add(event);
+ }
+
+ /// Send analytics for the event with the name [name].
+ /// When you cannot use Patapata's standard Analytics-related Widget or when you want to manually send events, you can follow this approach.
+ ///
+ /// example:
+ ///
+ /// ```dart
+ /// getApp().analytics.event(
+ /// name: 'Hogehoge Event',
+ /// );
+ /// ```
+ void event({
+ required String name,
+ Map? data,
+ BuildContext? context,
+ Level logLevel = Level.INFO,
+ }) {
+ AnalyticsContext? tAnalyticsContext;
+
+ if (context != null) {
+ tAnalyticsContext = Provider.of(context, listen: false);
+ } else {
+ tAnalyticsContext = globalContext;
+ }
+
+ final tEvent = AnalyticsEvent(
+ name: name,
+ data: data,
+ context: tAnalyticsContext,
+ );
+
+ tEvent._navigationInteractionContextData = navigationInteractionContextData;
+
+ _logger.log(logLevel, () => '$tEvent');
+ _eventStreamController.add(tEvent);
+ }
+
+ /// Send analytics for the [route].
+ /// Used when you want to send events related to page transitions.
+ ///
+ /// example:
+ ///
+ /// ```dart
+ /// getApp().analytics.routeViewEvent(
+ /// route: Route.of(context),
+ /// navigationType: AnalyticsNavigationType.push,
+ /// );
+ /// ```
+ void routeViewEvent(
+ Route route, {
+ String navigationType = AnalyticsNavigationType.push,
+ }) {
+ _promoteInteractionContextToNavigationContext();
+ rawEvent(
+ AnalyticsRouteViewEvent(
+ analytics: this,
+ route: route,
+ navigationType: navigationType,
+ ),
+ );
+ }
+
+ /// Send analytics for revenue.
+ /// 'Revenue' refers to numeric values related to sales or earnings.
+ ///
+ /// example:
+ ///
+ /// ```dart
+ /// getApp().analytics.revenueEvent(
+ /// revenue: 100.0,
+ /// );
+ /// ```
+ void revenueEvent({
+ required double revenue,
+ String? currency,
+ String? orderId,
+ String? receipt,
+ String? productId,
+ String? productName,
+ String? eventName,
+ Map? data,
+ AnalyticsContext? context,
+ Level logLevel = Level.INFO,
+ }) {
+ rawEvent(
+ AnalyticsRevenueEvent(
+ revenue: revenue,
+ currency: currency,
+ orderId: orderId,
+ receipt: receipt,
+ productId: productId,
+ productName: productName,
+ eventName: eventName,
+ context: context,
+ ),
+ logLevel: logLevel,
+ );
+ }
+
+ /// Convert the data type of [object] to int, double, or String using the default judgment and return it.
+ /// Additionally, in this function, data for the Analytics system trims the value side of key-value pairs to a maximum of 100 characters.
+ /// This is because the average length limitation for the value strings of third-party Analytics systems is around 100 characters.
+ static Object? defaultMakeLoggableToNative(Object? object) {
+ if (object == null) {
+ return '';
+ } else if (object is int || object is double || object is String) {
+ if (object is String) {
+ return object.characters.take(100).toString();
+ }
+
+ return object;
+ } else {
+ try {
+ return jsonEncode(object).characters.take(100).toString();
+ } catch (_) {
+ return object.toString();
+ }
+ }
+ }
+
+ static const _kMaxJsonParameterLength = 100;
+
+ /// Convert [object] into a loggable JSON parameter with the prefix [prefix].
+ static Map tryConvertToLoggableJsonParameters(
+ String prefix, Object? object) {
+ if (object == null) {
+ return const {};
+ }
+
+ final String tJson;
+
+ if (object is String) {
+ tJson = object;
+ } else if (object is int || object is double) {
+ tJson = object.toString();
+ } else {
+ try {
+ tJson = jsonEncode(object);
+ } catch (e) {
+ return const {};
+ }
+ }
+
+ final tMap = {};
+ final tCharacters = tJson.characters;
+
+ var i = 0;
+ for (;; i++) {
+ final tPart = tCharacters.getRange(
+ _kMaxJsonParameterLength * i, _kMaxJsonParameterLength * (i + 1));
+
+ if (tPart.isEmpty) {
+ break;
+ }
+
+ tMap['$prefix${i + 1}'] = tPart.toString();
+ }
+
+ return tMap;
+ }
+
+ @override
+ String toString() => 'interactionContext:$interactionContextData';
+}
+
+/// Analytics event class
+class AnalyticsEvent {
+ /// The name of this event.
+ final String name;
+
+ /// Analytics data.
+ final Map? data;
+
+ /// A Map created from the AnalyticsContext passed to this event.
+ final Map? contextData;
+
+ Map? _navigationInteractionContextData;
+
+ /// Data for NavigationInteractionContext.
+ Map? get navigationInteractionContextData =>
+ _navigationInteractionContextData;
+
+ /// Creates an [AnalyticsEvent].
+ /// Specify the name for this [AnalyticsEvent] to be created with [name], and pass the data for this event to [data].
+ /// If you provide [context], it will merge the data specified in [context] in to [data] with [data] being prioritized.
+ AnalyticsEvent({
+ required this.name,
+ this.data,
+ AnalyticsContext? context,
+ }) : contextData = context?.resolve();
+
+ /// A flat map from [contextData] and [data].
+ Map? get flatData => {}
+ ..addAll(contextData ?? const {})
+ ..addAll(data ?? const {})
+ ..removeWhere((key, value) => value == null);
+
+ @override
+ String toString() =>
+ 'AnalyticsEvent:$name: data=$data, context=$contextData, navigationInteractionContext=$navigationInteractionContextData';
+
+ @override
+ operator ==(Object other) => other is AnalyticsEvent
+ ? name == other.name &&
+ mapEquals(data, other.data) &&
+ mapEquals(contextData, contextData)
+ : false;
+
+ @override
+ int get hashCode => Object.hashAll([
+ name,
+ const MapEquality().hash(data),
+ const MapEquality().hash(contextData),
+ ]);
+}
+
+/// Context class for using analytics functionality,
+/// to be used in conjunction with [AnalyticsContextProvider].
+class AnalyticsContext {
+ final Map _data = {};
+ AnalyticsContext? _parent;
+
+ /// Creates a [AnalyticsContext] with event data [data].
+ AnalyticsContext(Map data) {
+ _data.addAll(data);
+ }
+
+ factory AnalyticsContext._withParent(
+ AnalyticsContext parent,
+ AnalyticsContext child,
+ ) =>
+ AnalyticsContext(
+ Map.from(child._data),
+ ).._parent = parent;
+
+ /// Recursively examines parent [AnalyticsContext]s,
+ /// and merges any data from them in to this [AnalyticsContext]'s data and returns the result.
+ Map resolve() => _parent != null
+ ? {
+ ..._parent!.resolve(),
+ ..._data,
+ }
+ : Map.from(_data);
+
+ @override
+ operator ==(Object other) => other is AnalyticsContext
+ ? _parent == other._parent && mapEquals(_data, other._data)
+ : false;
+
+ @override
+ int get hashCode => Object.hashAll([
+ _parent,
+ const MapEquality().hash(_data),
+ ]);
+
+ @override
+ String toString() => 'AnalyticsContext:${resolve()}';
+}
+
+class _MultiAnalyticsContext implements AnalyticsContext {
+ @override
+ AnalyticsContext? _parent;
+
+ @override
+ Map get _data => {
+ for (var i in _globalContextMap.values) ...i.resolve(),
+ };
+
+ final _globalContextMap =