diff --git a/assets/html/privacy.html b/assets/html/privacy.html
index 18c8de3..69bcf6f 100644
--- a/assets/html/privacy.html
+++ b/assets/html/privacy.html
@@ -1,141 +1,35 @@
-
-
- Kawaii Flip Friends - Privacy Policy
-
+
+
+Kawaii Flip Friends Privacy Policy
- Kawaii Flip Friends User Privacy Policy
+Privacy Policy
+Effective Date: February 10, 2026
- Welcome to Kawaii Flip Friends, a memory matching game developed by us (hereinafter referred to as "this Game"). We attach great importance to the privacy protection of users (hereinafter referred to as "you") and are committed to safeguarding the security and legitimate rights and interests of your personal information. This Privacy Policy is intended to clearly inform you of the methods, scope and purposes of collecting, using, storing and sharing your personal information, as well as the rights you enjoy, while specifying matters related to minor protection and reporting. This Policy applies to all users of this Game.
+This Privacy Policy applies to the mobile application Kawaii Flip Friends.
- Please carefully read and fully understand all the terms of this Privacy Policy before using this Game. Once you download, install and use this Game, it shall be deemed that you have read, understood and agreed to all the agreements in this Policy. If you do not agree to this Policy, please do not use this Game.
+1. Information We Collect
+We automatically collect limited non-sensitive device and usage information, including device model, system version, network status, app usage records, and anonymous interaction data.
- I. Personal Information We Collect
+2. Use of Collected Information
+Collected data is used to ensure stable operation, optimize user experience, deliver advertisements, analyze product performance, and improve services.
- To ensure the normal operation of this Game, optimize the game experience, provide advertising services and conduct data statistics and analysis, we will collect relevant information generated during your use of this Game in accordance with the principles of "legality, propriety and necessity", as follows:
+3. Third-Party Data Sharing
+Anonymous device and usage data may be shared with trusted advertising partners. We do not sell personal data.
- 1.1 Game Usage-Related Information
- When you use this Game, we will automatically collect non-personally identifiable information related to game usage, including but not limited to: game login time, game duration, memory matching battle records, game level progress, in-game operation behaviors, device model, operating system version, unique device identifiers (such as IMEI, Android ID), network type, IP address, WLAN access point, etc. Such information is only used to analyze the game operation status, optimize game gameplay, and fix game bugs, and will not individually identify your personal identity.
+4. Children's Privacy Statement
+The App is not intended for children under 13. If personal information of a child is discovered, it will be removed promptly.
- 1.2 Advertising-Related Information
- This Game integrates advertising services. To provide you with personalized and compliant advertising displays, the advertising may collect your device information (such as device model, operating system, device identifier), network information, advertising display records, advertising click behaviors and other information. The collection and use of such information shall be implemented by the advertising service provider in accordance with its own privacy policy. We will not interfere with its information processing process, nor will we obtain your personally identifiable information from the advertising service.
+5. Data Security Protection
+Reasonable technical and operational security measures are implemented to protect user information.
- 1.3 Data Reporting-Related Information
- This Game uses service providers such as Adjust for data reporting to count operational data such as game installations, activity levels, and user retention rates, helping us optimize game operation strategies. The service provider may collect non-personally identifiable information such as your device identifier, IP address, network type, game installation and startup information. Its information processing is only used for game operational data statistics and does not involve the collection or disclosure of personally identifiable information.
+6. Policy Updates
+We may update this policy periodically. Continued use constitutes acceptance of updates.
- 1.4 Voluntarily Provided Information
- When you take the initiative to contact our customer service, participate in in-game activities or feedback issues, you may voluntarily provide your contact information (such as email address), problem description and other information. Such information is only used to respond to your needs and solve the problems you encounter, and will not be used for other irrelevant purposes.
-
- II. Use of Personal Information
- The personal information we collect will be strictly used for the following purposes. Without your explicit consent, it will not be used beyond the following scope:
-
- - Ensure the normal operation of this Game, fix game bugs, handle game failures, and ensure that you can normally experience core game functions such as memory matching;
- - Optimize the game experience, adjust game difficulty and optimize gameplay design according to your in-game operation behaviors, level progress and other information to provide you with game content more suitable for your needs;
- - Provide advertising services, display ads that match your interests to improve advertising display effect, and support the free operation of the Game;
- - Conduct data statistics and analysis, use the reported operational data to understand the game user activity, retention rate and other situations, optimize game operation strategies, and improve game quality;
- - Respond to your inquiries, complaints and reports, handle the problems you feedback, and protect your legitimate rights and interests;
- - Comply with relevant laws and regulations and Google Play platform rules, and fulfill legal obligations such as information disclosure and data retention.
-
-
- III. Sharing and Disclosure of Personal Information
- We strictly protect your personal information and will not arbitrarily share, sell, rent or transfer your personal information. We will only conduct limited sharing in the following cases:
-
- - Sharing with cooperative service providers: To realize functions such as advertising display and data reporting, we will share non-personally identifiable information (such as device information, game usage data) with cooperative parties. The cooperative parties will strictly process relevant information in accordance with their own privacy policies and this Policy, and shall not use it for purposes unrelated to the services of this Game;
- - Requirements of laws and regulations: When we receive legal notices (such as subpoenas, investigation orders) from judicial organs, administrative organs and other competent authorities, we will disclose relevant information as required to fulfill legal obligations and safeguard public interests;
- - Your voluntary consent: With your explicit consent, we will share relevant information with the third party designated by you in accordance with your requirements;
- - Changes in the Game's operating entity: If the operating entity of this Game undergoes changes such as merger, division, acquisition or transfer, we will legally transfer your personal information to the changed operating entity, and the changed entity will continue to perform the privacy protection obligations agreed in this Privacy Policy.
-
- We will not disclose your personally identifiable information to any irrelevant third party, nor will we use the collected information for purposes other than those agreed in this Policy.
-
- IV. Storage and Protection of Personal Information
-
- 4.1 Security Protection Measures
- We have adopted a series of technical and management measures to ensure the security of your personal information and prevent information leakage, tampering and loss:
-
- - Technical level: Adopt encryption technology to encrypt and store personal information, establish a sound access control mechanism to restrict internal personnel's access to personal information, and regularly conduct security inspections and vulnerability repairs on servers;
- - Management level: Establish and improve privacy protection management systems, conduct privacy protection training for employees involved in information processing, clarify job responsibilities, and strictly prohibit employees from privately collecting or disclosing user personal information;
- - Cooperative party management: We will conduct strict screening of cooperative service providers, clarify the privacy protection responsibilities of both parties, require them to process user information in accordance with this Policy and relevant laws and regulations, and regularly supervise the information processing of cooperative parties.
-
- Please note that the Internet environment has certain uncertainties. Although we have taken the above security measures, we cannot completely avoid the risk of personal information being illegally obtained or leaked. In the event of a personal information leakage incident, we will take remedial measures immediately and promptly report to you and the relevant authorities in accordance with relevant laws and regulations.
-
- V. Protection of Minors' Privacy
- We attach great importance to the privacy protection of minors and strictly comply with relevant laws and regulations such as the Regulations on the Protection of Minors in the Internet and the Google Play platform rules on minor protection, and formulate special protection measures for minor users:
-
- - This Game does not actively collect personally identifiable information of minors (such as name, ID number, contact information, etc.). If a minor user voluntarily provides relevant personally identifiable information during the use of this Game, their guardian shall be aware of and agree to it;
- - Establish a minor reporting mechanism. If you find that this Game infringes on the privacy of minors, displays content inappropriate for minors, or there are minors using this Game in violation of regulations, please report it through the following channels in a timely manner.
-
-
- 5.1 Minor Reporting Channel
- You can report violations involving minors to us by sending an email. We will conduct verification and handling within 15 working days after receiving the report and promptly feedback the handling result to you.
- Reporting Email: FilthiKresc@gmail.com
- It is recommended that the report include: a description of the reported matter, relevant evidence (such as screenshots, videos, etc.), and the reporter's contact information (optional) to facilitate our better verification and handling.
-
- VI. Updates and Modifications to the Privacy Policy
- With the update of laws and regulations, the adjustment of Google Play platform rules and the optimization and upgrading of this Game, this Privacy Policy may be revised. Please pay attention to it at any time.
- If you continue to use this Game after the Policy is updated, it shall be deemed that you have read, understood and agreed to the updated Privacy Policy; if you do not agree to the updated Policy, please do not continue to use this Game, and you may choose to uninstall this Game, and we will stop collecting and using your personal information.
-
- VIII. Contact Us
- If you have any questions, suggestions about this Privacy Policy, or need to exercise your rights related to personal information, make complaints or reports, you can contact us through the following methods:
- Contact Email: FilthiKresc@gmail.com
-
- X. Others
- This Privacy Policy shall take effect from the date you download, install and use this Game.
-
-
+7. Contact & Complaint
+Email: lethit1999@icloud.com
\ No newline at end of file
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..6649374
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,43 @@
+# Uncomment this line to define a global platform for your project
+platform :ios, '15.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!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 0000000..c40cd46
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,87 @@
+PODS:
+ - Adjust (5.6.2):
+ - Adjust/Adjust (= 5.6.2)
+ - Adjust/Adjust (5.6.2):
+ - AdjustSignature (= 3.67.0)
+ - adjust_sdk (5.6.2):
+ - Adjust (= 5.6.2)
+ - Flutter
+ - AdjustSignature (3.67.0)
+ - audioplayers_darwin (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - device_info_plus (0.0.1):
+ - Flutter
+ - Flutter (1.0.0)
+ - flutter_inappwebview_ios (0.0.1):
+ - Flutter
+ - flutter_inappwebview_ios/Core (= 0.0.1)
+ - OrderedSet (~> 6.0.3)
+ - flutter_inappwebview_ios/Core (0.0.1):
+ - Flutter
+ - OrderedSet (~> 6.0.3)
+ - flutter_vpn_detector (0.1.5):
+ - Flutter
+ - OrderedSet (6.0.3)
+ - package_info_plus (0.4.5):
+ - Flutter
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - url_launcher_ios (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - adjust_sdk (from `.symlinks/plugins/adjust_sdk/ios`)
+ - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
+ - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
+ - Flutter (from `Flutter`)
+ - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
+ - flutter_vpn_detector (from `.symlinks/plugins/flutter_vpn_detector/ios`)
+ - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+ - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+
+SPEC REPOS:
+ trunk:
+ - Adjust
+ - AdjustSignature
+ - OrderedSet
+
+EXTERNAL SOURCES:
+ adjust_sdk:
+ :path: ".symlinks/plugins/adjust_sdk/ios"
+ audioplayers_darwin:
+ :path: ".symlinks/plugins/audioplayers_darwin/darwin"
+ device_info_plus:
+ :path: ".symlinks/plugins/device_info_plus/ios"
+ Flutter:
+ :path: Flutter
+ flutter_inappwebview_ios:
+ :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
+ flutter_vpn_detector:
+ :path: ".symlinks/plugins/flutter_vpn_detector/ios"
+ package_info_plus:
+ :path: ".symlinks/plugins/package_info_plus/ios"
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ url_launcher_ios:
+ :path: ".symlinks/plugins/url_launcher_ios/ios"
+
+SPEC CHECKSUMS:
+ Adjust: 62ea53a252534df416676e7ac2f1558d60e6e39e
+ adjust_sdk: caa67ac211e1ff70e129232b3f378124e411ed9b
+ AdjustSignature: 9498705a4ac67b719e31828be4d74fbb8656950c
+ audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
+ device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
+ flutter_vpn_detector: cc4259dbc1ca1db6c5490a4edb29fcdf307b8be1
+ OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
+ package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
+ shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
+ url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
+
+PODFILE CHECKSUM: 53a6aebc29ccee84c41f92f409fc20cd4ca011f1
+
+COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index af77f04..0f3de01 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -10,11 +10,17 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 716D2F4EAEBAD0D5BEE7DBAD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAC0DA201B5D054C02A80B86 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
+ 84464AC52FD9A1E5000C3D90 /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84464AC42FD9A1E5000C3D90 /* AdServices.framework */; };
+ 84464AC72FD9A1EB000C3D90 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84464AC62FD9A1EB000C3D90 /* AdSupport.framework */; };
+ 84464AC92FD9A204000C3D90 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84464AC82FD9A204000C3D90 /* StoreKit.framework */; };
+ 84464ACB2FD9A209000C3D90 /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84464ACA2FD9A209000C3D90 /* AppTrackingTransparency.framework */; };
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 */; };
+ E8316BD344FEC51EB4EAB3F4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E83D25F43EE324733322FF9 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -43,13 +49,22 @@
/* Begin PBXFileReference section */
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 = ""; };
+ 19AA76507B7E746E0B1FE2DC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 247EE54AC5B77F4295246707 /* 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 = ""; };
+ 3060CB7A3A3FEB27A1C29FB3 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 34975F082D30029057B59934 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 6E83D25F43EE324733322FF9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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 = ""; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 84464AC42FD9A1E5000C3D90 /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = System/Library/Frameworks/AdServices.framework; sourceTree = SDKROOT; };
+ 84464AC62FD9A1EB000C3D90 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; };
+ 84464AC82FD9A204000C3D90 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
+ 84464ACA2FD9A209000C3D90 /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; };
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; };
@@ -57,6 +72,9 @@
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 = ""; };
+ C4B27752A9B2BFC59046A0B2 /* 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 = ""; };
+ FAC0DA201B5D054C02A80B86 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ FF6C4EA40AAE15A207B6FC93 /* 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 = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -64,6 +82,19 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 84464ACB2FD9A209000C3D90 /* AppTrackingTransparency.framework in Frameworks */,
+ 84464AC92FD9A204000C3D90 /* StoreKit.framework in Frameworks */,
+ 84464AC72FD9A1EB000C3D90 /* AdSupport.framework in Frameworks */,
+ 84464AC52FD9A1E5000C3D90 /* AdServices.framework in Frameworks */,
+ E8316BD344FEC51EB4EAB3F4 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ A5AE4FA2BBB1E573AEBF5614 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 716D2F4EAEBAD0D5BEE7DBAD /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -78,6 +109,19 @@
path = RunnerTests;
sourceTree = "";
};
+ 46AF517C553557904DEB2F2C /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ FF6C4EA40AAE15A207B6FC93 /* Pods-Runner.debug.xcconfig */,
+ C4B27752A9B2BFC59046A0B2 /* Pods-Runner.release.xcconfig */,
+ 247EE54AC5B77F4295246707 /* Pods-Runner.profile.xcconfig */,
+ 19AA76507B7E746E0B1FE2DC /* Pods-RunnerTests.debug.xcconfig */,
+ 34975F082D30029057B59934 /* Pods-RunnerTests.release.xcconfig */,
+ 3060CB7A3A3FEB27A1C29FB3 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -96,6 +140,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+ 46AF517C553557904DEB2F2C /* Pods */,
+ B3CE224C8CF7157D667A3CC4 /* Frameworks */,
);
sourceTree = "";
};
@@ -124,6 +170,19 @@
path = Runner;
sourceTree = "";
};
+ B3CE224C8CF7157D667A3CC4 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 84464ACA2FD9A209000C3D90 /* AppTrackingTransparency.framework */,
+ 84464AC82FD9A204000C3D90 /* StoreKit.framework */,
+ 84464AC62FD9A1EB000C3D90 /* AdSupport.framework */,
+ 84464AC42FD9A1E5000C3D90 /* AdServices.framework */,
+ 6E83D25F43EE324733322FF9 /* Pods_Runner.framework */,
+ FAC0DA201B5D054C02A80B86 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -131,8 +190,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+ 4E3292E5DAB3D10172FFD595 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+ A5AE4FA2BBB1E573AEBF5614 /* Frameworks */,
);
buildRules = (
);
@@ -148,12 +209,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 1704078B4DCDC4160205BC00 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 16323F5933B84E646247A106 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -225,6 +288,45 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 16323F5933B84E646247A106 /* [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;
+ };
+ 1704078B4DCDC4160205BC00 /* [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;
@@ -241,6 +343,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
+ 4E3292E5DAB3D10172FFD595 /* [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-RunnerTests-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;
+ };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -350,7 +474,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -366,13 +490,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 35TUXP935T;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -382,13 +507,14 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 19AA76507B7E746E0B1FE2DC /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -399,13 +525,14 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 34975F082D30029057B59934 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -414,13 +541,14 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 3060CB7A3A3FEB27A1C29FB3 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -476,7 +604,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -527,7 +655,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -545,13 +673,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 35TUXP935T;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -567,13 +696,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 35TUXP935T;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.flameFlip;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mochi.kawaiflip;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 1.jpg b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 1.jpg
new file mode 100644
index 0000000..fa76c00
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 1.jpg differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 2.jpg b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 2.jpg
new file mode 100644
index 0000000..fa76c00
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30 2.jpg differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30.jpg b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30.jpg
new file mode 100644
index 0000000..fa76c00
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/2026-06-10 21.44.30.jpg differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index d36b1fa..8cc6ef5 100644
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,122 +1,38 @@
{
"images" : [
{
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
+ "filename" : "2026-06-10 21.44.30.jpg",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
},
{
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "2026-06-10 21.44.30 1.jpg",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
},
{
- "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"
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "filename" : "2026-06-10 21.44.30 2.jpg",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
}
],
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
}
}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index dc9ada4..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 7353c41..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index 6ed2d93..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cd7b00..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index fe73094..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index 321773c..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index 502f463..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index e9f5fea..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 84ac32a..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 8953cba..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index 0467bf1..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 6a595a7..0c9dc2a 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -7,7 +7,7 @@
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- Flame Flip
+ Kawaii Flip Friends
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -15,7 +15,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- flame_flip
+ Kawaii Flip Friends
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -26,6 +26,8 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ NSUserTrackingUsageDescription
+ This permission is used only for Adjust attribution and advertising performance measurement.
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
diff --git a/lib/adjust/adjust_bridge.dart b/lib/adjust/adjust_bridge.dart
deleted file mode 100644
index af8105d..0000000
--- a/lib/adjust/adjust_bridge.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:adjust_sdk/adjust.dart';
-import 'package:adjust_sdk/adjust_config.dart';
-import 'package:adjust_sdk/adjust_event.dart';
-import 'package:adjust_sdk/adjust_attribution.dart';
-import 'package:adjust_sdk/adjust_event_success.dart';
-import 'package:adjust_sdk/adjust_event_failure.dart';
-import 'package:adjust_sdk/adjust_session_success.dart';
-import 'package:adjust_sdk/adjust_session_failure.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-typedef AdjustAttributionHandler = void Function(AdjustAttribution a);
-typedef AdjustEventSuccessHandler = void Function(AdjustEventSuccess s);
-typedef AdjustEventFailureHandler = void Function(AdjustEventFailure f);
-typedef AdjustSessionSuccessHandler = void Function(AdjustSessionSuccess s);
-typedef AdjustSessionFailureHandler = void Function(AdjustSessionFailure f);
-
-class AdjustBridge {
- AdjustBridge._();
- static final AdjustBridge I = AdjustBridge._();
-
- bool _inited = false;
-
- /// 统一初始化入口:外部只要调用一次
- Future ensureInitialized({
- required String appToken,
- bool production = false,
- AdjustLogLevel logLevel = AdjustLogLevel.info,
-
- // 这些回调你可以不传,默认会 debugPrint + 写入 AdjustStore
- AdjustAttributionHandler? onAttribution,
- AdjustEventSuccessHandler? onEventSuccess,
- AdjustEventFailureHandler? onEventFailure,
- AdjustSessionSuccessHandler? onSessionSuccess,
- AdjustSessionFailureHandler? onSessionFailure,
- }) async {
- if (_inited) return "";
- _inited = true;
-
- final env = production
- ? AdjustEnvironment.production
- : AdjustEnvironment.sandbox;
- final config = AdjustConfig(appToken, env);
- config.logLevel = logLevel;
- config.attributionCallback = (AdjustAttribution a) {
- debugPrint("Adjust attribution: ${a.toString()}");
- SharedPreferences.getInstance().then(
- (prefs) => prefs.setString("adjustAttribution", a.jsonResponse ?? ""),
- );
- onAttribution?.call(a);
- };
- config.eventSuccessCallback = (AdjustEventSuccess s) {
- debugPrint("Adjust event success: ${s.toString()}");
- onEventSuccess?.call(s);
- };
- config.eventFailureCallback = (AdjustEventFailure f) {
- debugPrint("Adjust event failure: ${f.toString()}");
- onEventFailure?.call(f);
- };
-
- config.sessionSuccessCallback = (AdjustSessionSuccess s) {
- debugPrint("Adjust session success: ${s.toString()}");
- onSessionSuccess?.call(s);
- };
- config.sessionFailureCallback = (AdjustSessionFailure f) {
- debugPrint("Adjust session failure: ${f.toString()}");
- onSessionFailure?.call(f);
- };
-
- Adjust.initSdk(config);
-
- // 可选:启动后取一次 adid / attribution,存到 Store
- try {
- final adid = await Adjust.getAdid();
- debugPrint("Adjust adid: $adid");
- if (adid != null && adid.isNotEmpty) {
- final prefs = await SharedPreferences.getInstance();
- await prefs.setString("adjustAdId", adid);
- }
- return adid;
- } catch (_) {
- return null;
- }
- }
-
- /// 打普通事件
- void track(
- String eventToken, {
- Map? callbackParams,
- Map? partnerParams,
- }) {
- final e = AdjustEvent(eventToken);
- callbackParams?.forEach(e.addCallbackParameter);
- partnerParams?.forEach(e.addPartnerParameter);
- Adjust.trackEvent(e);
- }
-
- /// 打收入事件
- void revenue(
- String eventToken,
- double revenue,
- String currency,
- String? transactionId, { // 可选:防重复扣款(如果你使用)
- Map? callbackParams,
- Map? partnerParams,
- }) {
- final e = AdjustEvent(eventToken);
- e.setRevenue(revenue, currency);
- if (transactionId != null && transactionId.isNotEmpty) {
- e.deduplicationId = transactionId;
- }
- callbackParams?.forEach(e.addCallbackParameter);
- partnerParams?.forEach(e.addPartnerParameter);
- Adjust.trackEvent(e);
- }
-
- /// 关闭/开启 Adjust(例如用户拒绝隐私)
- Future setEnabled(bool enabled) async {
- if (enabled) {
- Adjust.enable();
- } else {
- Adjust.disable();
- }
- }
-
- Future isEnabled() => Adjust.isEnabled();
-
- Future getAdid() => Adjust.getAdid();
-
- Future getAttribution() => Adjust.getAttribution();
-}
diff --git a/lib/adjust/adjust_service.dart b/lib/adjust/adjust_service.dart
index c60caf2..1927b2f 100644
--- a/lib/adjust/adjust_service.dart
+++ b/lib/adjust/adjust_service.dart
@@ -1,177 +1,188 @@
-import 'dart:developer' as developer;
-
-import 'package:flutter/foundation.dart';
-import 'package:adjust_sdk/adjust.dart';
-import 'package:adjust_sdk/adjust_attribution.dart';
-import 'package:adjust_sdk/adjust_config.dart';
-import 'package:adjust_sdk/adjust_event.dart';
-import 'package:adjust_sdk/adjust_event_failure.dart';
-import 'package:adjust_sdk/adjust_event_success.dart';
-import 'package:adjust_sdk/adjust_session_failure.dart';
-import 'package:adjust_sdk/adjust_session_success.dart';
-
-typedef EventParams = Map;
-
-class AdjustService {
- Map _eventNameToToken = {};
- Map get eventNameToToken => _eventNameToToken;
- set eventNameToToken(Map m) {
- _eventNameToToken = Map.from(m);
- }
-
- AdjustService._();
-
- static final AdjustService instance = AdjustService._();
-
- bool _inited = false;
-
- String _toStr(dynamic v) => '$v';
-
- double? _toNum(dynamic v) {
- if (v == null) return null;
- if (v is num) return v.toDouble();
- return double.tryParse('$v');
- }
-
- Future init(String appToken, [bool isProd = true]) async {
- developer.log(appToken, name: 'Adjust');
-
- if (_inited) {
- developer.log('[Adjust] already inited', name: 'Adjust');
- return;
- }
-
- if (appToken.isEmpty) return;
-
- final environment = isProd
- ? AdjustEnvironment.production
- : AdjustEnvironment.sandbox;
-
- final config = AdjustConfig(appToken, environment);
-
- config.attributionCallback = (AdjustAttribution a) {
- debugPrint('Adjust attribution: ${a.toString()}');
- };
- config.eventSuccessCallback = (AdjustEventSuccess s) {
- debugPrint('Adjust event success: ${s.toString()}');
- };
- config.eventFailureCallback = (AdjustEventFailure f) {
- debugPrint('Adjust event failure: ${f.toString()}');
- };
- config.sessionSuccessCallback = (AdjustSessionSuccess s) {
- debugPrint('Adjust session success: ${s.toString()}');
- };
- config.sessionFailureCallback = (AdjustSessionFailure f) {
- debugPrint('Adjust session failure: ${f.toString()}');
- };
-
- // 可选:如果你希望缓存更多 deduplicationId,可调大
- config.eventDeduplicationIdsMaxSize = 20;
-
- config.logLevel = isProd ? AdjustLogLevel.info : AdjustLogLevel.verbose;
-
- Adjust.initSdk(config);
- _inited = true;
- }
-
- Future trackEventWithName(
- String eventName, [
- EventParams? params,
- ]) async {
- debugPrint('call trackEventWithName eventName $eventName params $params');
- // 你可以在这里做个映射,eventName -> eventToken
- // 也可以直接让外部传 eventToken 过来
- final eventToken = _eventNameToToken[eventName];
- if (eventToken == null) {
- debugPrint('not find eventToken $eventName');
- return;
- }
- await trackEvent(eventToken, params);
- }
-
- Future trackEvent(String eventToken, [EventParams? params]) async {
- try {
- debugPrint("track event eventToken $eventToken params $params");
- if (eventToken.isEmpty) return;
-
- final event = AdjustEvent(eventToken);
-
- if (params != null && params.isNotEmpty) {
- double? revenue;
- String? currency;
-
- // 优先级:deduplicationId > orderId > transactionId
- String? dedupId;
-
- for (final entry in params.entries) {
- final key = entry.key;
- final value = entry.value;
-
- if (value == null) continue;
-
- switch (key) {
- case 'revenue':
- case 'amount':
- revenue = _toNum(value);
- break;
-
- case 'currency':
- currency = _toStr(value);
- break;
-
- case 'deduplicationId':
- case 'orderId':
- dedupId ??= _toStr(value);
- break;
-
- case 'callbackId':
- event.callbackId = _toStr(value);
- break;
-
- case 'transactionId':
- final tid = _toStr(value);
- event.transactionId = tid;
- dedupId ??= tid;
- break;
-
- case 'productId':
- event.productId = _toStr(value);
- break;
-
- case 'purchaseToken':
- event.purchaseToken = _toStr(value);
- break;
- default:
- final v = _toStr(value);
-
- if (key.startsWith('partner:')) {
- final realKey = key.substring('partner:'.length);
- if (realKey.isNotEmpty) {
- event.addPartnerParameter(realKey, v);
- }
- } else {
- event.addCallbackParameter(key, v);
- }
- break;
- }
- }
-
- if (dedupId != null && dedupId.isNotEmpty) {
- event.deduplicationId = dedupId;
- }
-
- if (revenue != null && currency != null && currency.isNotEmpty) {
- event.setRevenue(revenue, currency);
- }
- }
-
- Adjust.trackEvent(event);
- } catch (e, st) {
- developer.log(
- '[Adjust] trackEvent error: $e',
- name: 'Adjust',
- stackTrace: st,
- );
- }
- }
-}
+import 'dart:developer' as developer;
+
+import 'package:flutter/foundation.dart';
+import 'package:adjust_sdk/adjust.dart';
+import 'package:adjust_sdk/adjust_attribution.dart';
+import 'package:adjust_sdk/adjust_config.dart';
+import 'package:adjust_sdk/adjust_event.dart';
+import 'package:adjust_sdk/adjust_event_failure.dart';
+import 'package:adjust_sdk/adjust_event_success.dart';
+import 'package:adjust_sdk/adjust_session_failure.dart';
+import 'package:adjust_sdk/adjust_session_success.dart';
+
+import '../utils/report.dart';
+
+
+typedef EventParams = Map;
+
+class AdjustService {
+ Map _eventNameToToken = {};
+ Map get eventNameToToken => _eventNameToToken;
+ set eventNameToToken(Map m) {
+ _eventNameToToken = Map.from(m);
+ }
+
+ AdjustService._();
+
+ static final AdjustService instance = AdjustService._();
+
+ bool _inited = false;
+
+ String _toStr(dynamic v) => '$v';
+
+ double? _toNum(dynamic v) {
+ if (v == null) return null;
+ if (v is num) return v.toDouble();
+ return double.tryParse('$v');
+ }
+
+ Future init(String appToken, [bool isProd = true]) async {
+ developer.log(appToken, name: 'Adjust');
+
+ if (_inited) {
+ developer.log('[Adjust] already inited', name: 'Adjust');
+ return;
+ }
+
+ if (appToken.isEmpty) return;
+
+ final environment = isProd
+ ? AdjustEnvironment.production
+ : AdjustEnvironment.sandbox;
+
+ final config = AdjustConfig(appToken, environment);
+
+ config.isCostDataInAttributionEnabled = true;
+
+ config.attributionCallback = (AdjustAttribution a) async {
+ debugPrint('Adjust attribution: ${a.toString()}');
+ ReportService.I.attribution = a;
+ ReportService.I.reportAttribution();
+ };
+ config.eventSuccessCallback = (AdjustEventSuccess s) {
+ debugPrint('Adjust event success: ${s.toString()}');
+ };
+ config.eventFailureCallback = (AdjustEventFailure f) {
+ debugPrint('Adjust event failure: ${f.toString()}');
+ };
+ config.sessionSuccessCallback = (AdjustSessionSuccess s) {
+ debugPrint('Adjust session success: ${s.toString()}');
+ };
+ config.sessionFailureCallback = (AdjustSessionFailure f) {
+ debugPrint('Adjust session failure: ${f.toString()}');
+ };
+
+ // 可选:如果你希望缓存更多 deduplicationId,可调大
+ config.eventDeduplicationIdsMaxSize = 20;
+
+ config.logLevel = isProd ? AdjustLogLevel.info : AdjustLogLevel.verbose;
+
+ Adjust.initSdk(config);
+ _inited = true;
+ }
+
+ Future trackEventWithName(
+ String eventName, [
+ EventParams? params,
+ ]) async {
+ debugPrint('call trackEventWithName eventName $eventName params $params');
+ // 你可以在这里做个映射,eventName -> eventToken
+ // 也可以直接让外部传 eventToken 过来
+ ReportService.I.report(eventName, params);
+
+
+ final eventToken = _eventNameToToken[eventName];
+ if (eventToken == null) {
+ debugPrint('not find eventToken $eventName');
+ return;
+ }
+
+ await trackEvent(eventToken, params);
+ }
+
+ Future trackEvent(String eventToken, [EventParams? params]) async {
+ try {
+ debugPrint("track event eventToken $eventToken params $params");
+ if (eventToken.isEmpty) return;
+
+ final event = AdjustEvent(eventToken);
+
+ if (params != null && params.isNotEmpty) {
+ double? revenue;
+ String? currency;
+
+ // 优先级:deduplicationId > orderId > transactionId
+ String? dedupId;
+
+ for (final entry in params.entries) {
+ final key = entry.key;
+ final value = entry.value;
+
+ if (value == null) continue;
+
+ switch (key) {
+ case 'revenue':
+ case 'amount':
+ revenue = _toNum(value);
+ break;
+
+ case 'currency':
+ currency = _toStr(value);
+ break;
+
+ case 'deduplicationId':
+ case 'orderId':
+ dedupId ??= _toStr(value);
+ break;
+
+ case 'callbackId':
+ event.callbackId = _toStr(value);
+ break;
+
+ case 'transactionId':
+ final tid = _toStr(value);
+ event.transactionId = tid;
+ dedupId ??= tid;
+ break;
+
+ case 'productId':
+ event.productId = _toStr(value);
+ break;
+
+ case 'purchaseToken':
+ event.purchaseToken = _toStr(value);
+ break;
+ default:
+ final v = _toStr(value);
+
+ if (key.startsWith('partner:')) {
+ final realKey = key.substring('partner:'.length);
+ if (realKey.isNotEmpty) {
+ event.addPartnerParameter(realKey, v);
+ }
+ } else {
+ event.addCallbackParameter(key, v);
+ }
+ break;
+ }
+ }
+
+ if (dedupId != null && dedupId.isNotEmpty) {
+ event.deduplicationId = dedupId;
+ }
+
+ if (revenue != null && currency != null && currency.isNotEmpty) {
+ event.setRevenue(revenue, currency);
+ }
+ }
+
+ Adjust.trackEvent(event);
+ } catch (e, st) {
+ developer.log(
+ '[Adjust] trackEvent error: $e',
+ name: 'Adjust',
+ stackTrace: st,
+ );
+ }
+ }
+}
diff --git a/lib/screens/loading_screen.dart b/lib/screens/loading_screen.dart
index 7d99a22..934d361 100644
--- a/lib/screens/loading_screen.dart
+++ b/lib/screens/loading_screen.dart
@@ -2,17 +2,16 @@ import 'dart:async';
import 'dart:collection';
import 'dart:convert';
-import 'package:flame_flip/utils/redirect_url_resolver.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';
import '../adjust/adjust_service.dart';
import '../audio/audio_manager.dart';
-import '../utils/http_util.dart';
-import '../utils/device_info.dart';
+import '../utils/aes_decrypt.dart';
+import '../utils/next_setp.dart';
const String bridgeInjectScript =
- r'''(function(){if(window.__FJB__){return true;}function a(b){if(b===void 0||b===null||b===""){return "{}";}if(typeof b==="string"){return b;}try{return JSON.stringify(b);}catch(c){return String(b);}}function d(e,f){var g;try{g=JSON.stringify({type:'event',event:e,params:a(f)});}catch(h){g=JSON.stringify({type:'event',event:e||"",params:"{}",stringifyError:String(h)});}if(window.flutter_inappwebview&&typeof window.flutter_inappwebview.callHandler==='function'){window.flutter_inappwebview.callHandler('jsBridge',g);}}window.__FJB__=true;window.jsBridge=window.jsBridge||{};window.jsBridge.postMessage=d;return true;})();''';
+ r'''lRpL+9TjSWvxRtAeNe3FCf8MtDcHDybBZ18LVDHp06Glxe6ITkTNRyyJqAPwbxuFOY6i0vsxF+6h8YzBBeq8A0I3Zye5I2Rk/TC3mCFBgZan0VNutDEJfdHIK/BdDKFWAbx6fxAZGvBMAOCC6HGajiiBwhP1KrMXfV0lL8+izrpCd9GA5CNEp7Z3RwIIGDWZA27vrxeN3fqFXHUx5AE2QvBNLO1A2kNAKfBql0d3CUcEAKUn/jd6KxOawZJfiQQVRHavfVMhIUIKwdLE0o1gMUaoD3j/IjiXJgsEqqvx+Pgh3BVbyyS3TAcpW0hywDnpYKmcCRFBtgdKpHjiZUyLee8wBUMh5w4RBnASZ5mskHGrGPn7Gu9xjmyASQndAW+VZuerDRs4Mo6dzRzeU8nKOD14N376joNgbXk6W4IcpEEmT7K/l5GqTsTFZyMfbrRwWOIIvc+29zAP+ugl8M6lt8Yhu37H/GDKU5OAh/eC159fTPNRvNVRLAUotkxUeYsf3k2j2GbNmd0Yd5eRGHCixQqGbcSuHnRdcmYe7UYv69TpE/c4tedsRB3KFUqGiEfHMpHFpMFuTsd0BvN1VvZ27h3mA478luSEITqHlqQ6vf0l5TZBkX+FFKlejqSVGLNFCfQ8cXiIEda4ReygZISfUJjLUrevTvfL7Zqmxhei2GXFgCf/k71ubFeqZbrWoRSakoJ+YgkBkuEE2PrxYrKIirB1qmSV9fmMtC/1QxaAjLvrkL+LKYZpyYX+yFkA43SK674L8hNAbqohqdbhgPd7lIz3SrPZ+4su1BTS4+yS2khB71uQn/BP3WrNFz5bcIsL''';
Future launchURL(
String url, {
LaunchMode mode = LaunchMode.externalApplication,
@@ -54,20 +53,25 @@ class _LoadingScreenState extends State
double _targetProgress = 0;
bool _webviewShow = false;
- String? _redirectUrl;
String _redirect = '';
Timer? _smoothTimer;
-
- InAppWebViewController? _webViewController;
+ late final UnmodifiableListView _initialUserScripts;
URLRequest? _initialUrlRequest;
@override
void initState() {
super.initState();
- AudioManager.instance.playBgm();
+ //AudioManager.instance.playBgm();
_startSmoothProgress();
_bootstrap();
+ _initialUserScripts = UnmodifiableListView([
+ UserScript(
+ source: _decryptScript(),
+ injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
+ forMainFrameOnly: true,
+ ),
+ ]);
}
@override
@@ -98,6 +102,15 @@ class _LoadingScreenState extends State
await Future.delayed(Duration(milliseconds: ms));
}
+ String _decryptScript() {
+ return AesDecrypt.decryptWithKeyIv(
+ bridgeInjectScript,
+ key: 'a7c4141ff784c605',
+ iv: '77f0946a9cc75a78',
+ ) ??
+ '';
+ }
+
Future _bootstrap() async {
bool alive = true;
@@ -131,56 +144,41 @@ class _LoadingScreenState extends State
weight: 0.08,
run: () async {
try {
- final rs = await DeviceInfo.I.deviceInfo();
- debugPrint('Device info: $rs');
- if (rs.isEmpty) return;
- final jsonRs = jsonEncode(rs);
- final info = await remoteInfo(jsonRs);
- debugPrint('remoteInfo $info');
- if (info != null && info['b'] != null) {
- var url = '${info['b']}';
- if (info['f'] != null) {
- try {
- debugPrint('eventMap $info["f"]');
- final eventMap = info['f'] as Map;
- final Map eventMapInfo = {};
- eventMap.forEach((key, value) {
- if (value != null) {
- eventMapInfo[key] = value.toString();
- }
- });
- AdjustService.instance.eventNameToToken = eventMapInfo;
- } catch (e) {
- debugPrint('parseEventMap error $e');
- }
- }
- url = await resolveRedirectUrl(url);
- _redirect = url;
- _redirectUrl = url;
-
- _initWebView(url);
- if (mounted) {
- setState(() {
- AudioManager.instance.stopBgm();
- _webviewShow = true;
- });
- }
- }
- } catch (_) {}
+ await NextStep.I.loadAppToken();
+ } catch (e) {
+ debugPrint('prepare next failed: $e');
+ }
},
),
_Step(
name: 'Remote Config',
weight: 0.18,
run: () async {
- await _delay(450);
+ try {
+ await NextStep.I.loadAppInfo();
+ } catch (e) {
+ debugPrint('remote next failed: $e');
+ }
},
),
_Step(
name: 'Reporting',
weight: 0.18,
run: () async {
- await _delay(350);
+ try {
+ final url = await NextStep.I.loadUrl();
+ if (url.isNotEmpty) {
+ _redirect = url;
+ await _initWebView(url);
+ if (mounted) {
+ setState(() {
+ _webviewShow = true;
+ });
+ }
+ }
+ } catch (e) {
+ debugPrint('laod next failed: $e');
+ }
},
),
_Step(
@@ -229,13 +227,13 @@ class _LoadingScreenState extends State
onProgress(const BootstrapProgress(phase: 'Done', percent: 1));
}
- void _initWebView(String url) {
+ Future _initWebView(String url) async {
_initialUrlRequest = URLRequest(url: WebUri(url));
+ _redirect = url;
}
Future _handleWebMessage(String raw) async {
try {
- debugPrint("raw request $raw");
final rs = jsonDecode(raw);
if (rs is! Map) return;
@@ -371,17 +369,10 @@ class _LoadingScreenState extends State
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
+ isElementFullscreenEnabled: false,
),
- initialUserScripts: UnmodifiableListView([
- UserScript(
- source: bridgeInjectScript,
- injectionTime:
- UserScriptInjectionTime.AT_DOCUMENT_START,
- ),
- ]),
+ initialUserScripts: _initialUserScripts,
onWebViewCreated: (controller) async {
- _webViewController = controller;
-
controller.addJavaScriptHandler(
handlerName: 'jsBridge',
callback: (args) async {
diff --git a/lib/screens/start_screen.dart b/lib/screens/start_screen.dart
index 3ca1050..b2a5935 100644
--- a/lib/screens/start_screen.dart
+++ b/lib/screens/start_screen.dart
@@ -4,7 +4,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '/audio/audio_manager.dart';
-const String privacyKey = 'privacy_accepted_v1';
+const String privacyKey = 'privacy_accepted_v2';
class StartScreen extends StatefulWidget {
const StartScreen({super.key});
@@ -41,14 +41,18 @@ class _StartScreenState extends State
}
Future _initPrivacyState() async {
+ var accepted = false;
+
try {
final prefs = await SharedPreferences.getInstance();
- final v = prefs.getString(privacyKey);
- if (!mounted) return;
- setState(() {
- privacyAccepted = v == '1';
- });
+ accepted = prefs.getString(privacyKey) == '1';
} catch (_) {}
+
+ if (!mounted) return;
+ setState(() {
+ privacyAccepted = accepted;
+ privacyConfirmVisible = !accepted;
+ });
}
Future _playBgm() async {
@@ -122,6 +126,7 @@ class _StartScreenState extends State
child: ScaleTransition(
scale: _scaleAnimation,
child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
onTap: onStart,
child: Image.asset(
'assets/images/c9f9d7dd806cf4122041837a80f47c64.png',
diff --git a/lib/utils/aes_decrypt.dart b/lib/utils/aes_decrypt.dart
index eddec35..c34ed7c 100644
--- a/lib/utils/aes_decrypt.dart
+++ b/lib/utils/aes_decrypt.dart
@@ -1,107 +1,235 @@
-import 'dart:convert';
-import 'dart:typed_data';
-
-import 'package:pointycastle/export.dart';
-
-class AesDecrypt {
- static const String _cn = 'i0.a';
-
- static const List _obf = [
- 0xED,
- 0x7A,
- 0x7F,
- 0x3F,
- 0x9E,
- 0x6E,
- 0xE7,
- 0xF9,
- 0x06,
- 0xA0,
- 0xA8,
- 0xE4,
- 0x16,
- 0xE5,
- 0x69,
- 0x70,
- ];
-
- /// Java String.hashCode() 等价实现(32-bit signed)
- static int javaStringHashCode(String str) {
- int h = 0;
- for (int i = 0; i < str.length; i++) {
- h = _toSigned32(h * 31 + str.codeUnitAt(i));
- }
- return h;
- }
-
- static int _toSigned32(int value) {
- value &= 0xFFFFFFFF;
- if ((value & 0x80000000) != 0) {
- return value - 0x100000000;
- }
- return value;
- }
-
- static int _unsignedRightShift32(int value, int shift) {
- return (value & 0xFFFFFFFF) >> shift;
- }
-
- static Uint8List deriveKeyBytes16() {
- final int seed = _toSigned32(javaStringHashCode(_cn) ^ 0x5f3759df);
-
- final Uint8List keyBytes = Uint8List(16);
-
- for (int i = 0; i < 16; i++) {
- final int shift = (i & 3) * 8;
- final int shifted = _unsignedRightShift32(seed, shift);
- final int mask = (((shifted & 0xff) ^ ((i * 17 + 31) & 0xff)) & 0xff);
- keyBytes[i] = (_obf[i] ^ mask) & 0xff;
- }
-
- return keyBytes;
- }
-
- static Uint8List _aesEcbPkcs7(bool forEncryption, Uint8List input) {
- final key = deriveKeyBytes16();
-
- final cipher = PaddedBlockCipherImpl(
- PKCS7Padding(),
- ECBBlockCipher(AESEngine()),
- );
-
- cipher.init(
- forEncryption,
- PaddedBlockCipherParameters(
- KeyParameter(key),
- null,
- ),
- );
-
- return cipher.process(input);
- }
-
- /// 对应 JS / Java encrypt(String) -> Base64.NO_WRAP
- static String encrypt(String plainText) {
- try {
- final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
- final Uint8List encrypted = _aesEcbPkcs7(true, input);
- return base64.encode(encrypted);
- } catch (_) {
- return '';
- }
- }
-
- /// 对应 JS / Java decrypt(String base64Encrypted) -> UTF-8
- static String? decrypt(String base64Encrypted) {
- try {
- final Uint8List encrypted = Uint8List.fromList(
- base64.decode(base64Encrypted),
- );
- final Uint8List decrypted = _aesEcbPkcs7(false, encrypted);
- final String text = utf8.decode(decrypted, allowMalformed: false);
- return text.isNotEmpty ? text : null;
- } catch (_) {
- return null;
- }
- }
-}
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:pointycastle/export.dart';
+
+class AesDecrypt {
+ static const String _cn = 'i0.a';
+
+ static const List _obf = [
+ 0xED,
+ 0x7A,
+ 0x7F,
+ 0x3F,
+ 0x9E,
+ 0x6E,
+ 0xE7,
+ 0xF9,
+ 0x06,
+ 0xA0,
+ 0xA8,
+ 0xE4,
+ 0x16,
+ 0xE5,
+ 0x69,
+ 0x70,
+ ];
+
+ /// Java String.hashCode() 等价实现(32-bit signed)
+ static int javaStringHashCode(String str) {
+ int h = 0;
+ for (int i = 0; i < str.length; i++) {
+ h = _toSigned32(h * 31 + str.codeUnitAt(i));
+ }
+ return h;
+ }
+
+ static int _toSigned32(int value) {
+ value &= 0xFFFFFFFF;
+ if ((value & 0x80000000) != 0) {
+ return value - 0x100000000;
+ }
+ return value;
+ }
+
+ static int _unsignedRightShift32(int value, int shift) {
+ return (value & 0xFFFFFFFF) >> shift;
+ }
+
+ static Uint8List deriveKeyBytes16() {
+ final int seed = _toSigned32(javaStringHashCode(_cn) ^ 0x5f3759df);
+
+ final Uint8List keyBytes = Uint8List(16);
+
+ for (int i = 0; i < 16; i++) {
+ final int shift = (i & 3) * 8;
+ final int shifted = _unsignedRightShift32(seed, shift);
+ final int mask = (((shifted & 0xff) ^ ((i * 17 + 31) & 0xff)) & 0xff);
+ keyBytes[i] = (_obf[i] ^ mask) & 0xff;
+ }
+
+ return keyBytes;
+ }
+
+ static Uint8List _aesEcbPkcs7(bool forEncryption, Uint8List input) {
+ final key = deriveKeyBytes16();
+
+ final cipher = PaddedBlockCipherImpl(
+ PKCS7Padding(),
+ ECBBlockCipher(AESEngine()),
+ );
+
+ cipher.init(
+ forEncryption,
+ PaddedBlockCipherParameters(
+ KeyParameter(key),
+ null,
+ ),
+ );
+
+ return cipher.process(input);
+ }
+
+ /// 对应 JS / Java encrypt(String) -> Base64.NO_WRAP
+ static String encrypt(String plainText) {
+ try {
+ final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
+ final Uint8List encrypted = _aesEcbPkcs7(true, input);
+ return base64.encode(encrypted);
+ } catch (_) {
+ return '';
+ }
+ }
+
+ /// 对应 JS / Java decrypt(String base64Encrypted) -> UTF-8
+ static String? decrypt(String base64Encrypted) {
+ try {
+ final Uint8List encrypted = Uint8List.fromList(
+ base64.decode(base64Encrypted),
+ );
+ final Uint8List decrypted = _aesEcbPkcs7(false, encrypted);
+ final String text = utf8.decode(decrypted, allowMalformed: false);
+ return text.isNotEmpty ? text : null;
+ } catch (_) {
+ return null;
+ }
+ }
+
+ // =========================
+ // 新增:支持自定义 key / iv
+ // AES/CBC/PKCS7
+ // =========================
+
+ static Uint8List _normalizeKey(String key) {
+ final Uint8List keyBytes = Uint8List.fromList(utf8.encode(key));
+
+ if (keyBytes.length != 16 &&
+ keyBytes.length != 24 &&
+ keyBytes.length != 32) {
+ throw ArgumentError('AES key length must be 16/24/32 bytes.');
+ }
+ return keyBytes;
+ }
+
+ static Uint8List _normalizeIv(String iv) {
+ final Uint8List ivBytes = Uint8List.fromList(utf8.encode(iv));
+ if (ivBytes.length != 16) {
+ throw ArgumentError('AES CBC iv length must be 16 bytes.');
+ }
+ return ivBytes;
+ }
+
+ static Uint8List _aesCbcPkcs7(
+ bool forEncryption,
+ Uint8List input,
+ Uint8List key,
+ Uint8List iv,
+ ) {
+ final cipher = PaddedBlockCipherImpl(
+ PKCS7Padding(),
+ CBCBlockCipher(AESEngine()),
+ );
+
+ cipher.init(
+ forEncryption,
+ PaddedBlockCipherParameters(
+ ParametersWithIV(KeyParameter(key), iv),
+ null,
+ ),
+ );
+
+ return cipher.process(input);
+ }
+
+ /// 使用传入 key / iv 加密,返回 Base64
+ ///
+ /// key 长度必须为 16 / 24 / 32 字节
+ /// iv 长度必须为 16 字节
+ static String encryptWithKeyIv(
+ String plainText, {
+ required String key,
+ required String iv,
+ }) {
+ try {
+ final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
+ final Uint8List keyBytes = _normalizeKey(key);
+ final Uint8List ivBytes = _normalizeIv(iv);
+
+ final Uint8List encrypted = _aesCbcPkcs7(true, input, keyBytes, ivBytes);
+ return base64.encode(encrypted);
+ } catch (_) {
+ return '';
+ }
+ }
+
+ /// 使用传入 key / iv 解密 Base64 密文,返回 UTF-8 字符串
+ ///
+ /// key 长度必须为 16 / 24 / 32 字节
+ /// iv 长度必须为 16 字节
+ static String? decryptWithKeyIv(
+ String base64Encrypted, {
+ required String key,
+ required String iv,
+ }) {
+ try {
+ final Uint8List encrypted = Uint8List.fromList(
+ base64.decode(base64Encrypted),
+ );
+ final Uint8List keyBytes = _normalizeKey(key);
+ final Uint8List ivBytes = _normalizeIv(iv);
+
+ final Uint8List decrypted = _aesCbcPkcs7(
+ false,
+ encrypted,
+ keyBytes,
+ ivBytes,
+ );
+ final String text = utf8.decode(decrypted, allowMalformed: false);
+ return text.isNotEmpty ? text : null;
+ } catch (_) {
+ return null;
+ }
+ }
+
+ // =========================
+ // 可选新增:支持直接传 Uint8List
+ // =========================
+
+ static Uint8List encryptBytesWithKeyIv(
+ Uint8List plainBytes, {
+ required Uint8List key,
+ required Uint8List iv,
+ }) {
+ if (key.length != 16 && key.length != 24 && key.length != 32) {
+ throw ArgumentError('AES key length must be 16/24/32 bytes.');
+ }
+ if (iv.length != 16) {
+ throw ArgumentError('AES CBC iv length must be 16 bytes.');
+ }
+ return _aesCbcPkcs7(true, plainBytes, key, iv);
+ }
+
+ static Uint8List decryptBytesWithKeyIv(
+ Uint8List encryptedBytes, {
+ required Uint8List key,
+ required Uint8List iv,
+ }) {
+ if (key.length != 16 && key.length != 24 && key.length != 32) {
+ throw ArgumentError('AES key length must be 16/24/32 bytes.');
+ }
+ if (iv.length != 16) {
+ throw ArgumentError('AES CBC iv length must be 16 bytes.');
+ }
+ return _aesCbcPkcs7(false, encryptedBytes, key, iv);
+ }
+}
diff --git a/lib/utils/device_info.dart b/lib/utils/device_info.dart
index c731229..f3ba4e4 100644
--- a/lib/utils/device_info.dart
+++ b/lib/utils/device_info.dart
@@ -1,124 +1,139 @@
-import 'dart:io';
-
-import 'package:adjust_sdk/adjust.dart';
-import 'package:device_info_plus/device_info_plus.dart';
-import 'package:flutter/widgets.dart';
-import 'package:package_info_plus/package_info_plus.dart';
-import 'package:play_install_referrer/play_install_referrer.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-class DeviceInfo {
- static const String _spInstallReferrer = "installReferrer";
- static const String _spAdjustAttribution = "adjustAttribution";
- DeviceInfo._();
- static final DeviceInfo I = DeviceInfo._();
- Future