What changed, and why it matters
This is a large work-in-progress commit for a Monero wallet app. It mainly adds iOS support (CocoaPods setup, a bundled MoneroWallet framework, notification and logging export features) and hardens error handling in the Tor/SOCKS networking code. There is no clear security fix or vulnerability being patched. A few debug leftovers, such as a hardcoded '+1' in transaction counting and a test notification fired on every wallet home screen load, look like unfinished development code rather than intentional malicious changes. The bundled binary framework cannot be inspected from the diff, so its provenance and safety are unknown.
Treat this as unfinished development work, not a released security patch. Before merging or releasing: (1) remove the 'FIXME +1' hack and the test notification call in wallet_home.dart; (2) verify the origin, version, and reproducible build of the MoneroWallet.framework binary, and ideally replace it with a source-built or audited dependency; (3) review exported logs for sensitive wallet/transaction data and add scrubbing or warnings; (4) audit the custom SOCKS socket implementation for resource leaks and exception safety; (5) request a descriptive commit message and changelog entry from the developer.
Security signals we found
Bundled native framework (ios/Frameworks/MoneroWallet.framework/MoneroWallet) added as opaque binary blob with no source or build provenance
Debug/test code left in production paths: hardcoded '+1' transaction count and unconditional incoming-transaction notification
Background fetch mode (UIBackgroundModes fetch) enabled in both the app and the bundled framework
Verbose logging now exportable on iOS via system share sheet, increasing potential sensitive-data exposure surface if logs contain wallet/transaction data
SOCKS/Tor networking error handling improved but still relies on custom socket implementation
No vendor statement or CVE references supplied
Evidence from the diff
The commit is titled ‘wip’ and touches 32 files. iOS build plumbing is added: Podfile/Podfile.lock, Xcode project integration of Pods and a local MoneroWallet.framework, deployment target bumped to iOS 13/15, background fetch mode enabled, and notification delegate wiring. Dart changes add iOS-specific localization strings, an iOS log export flow using share_plus, and improved exception handling/logging in main.dart, fiat_rate_model.dart, socks_http.dart and socks_socket.dart. The SOCKS socket stream controllers were made late-initialized and error forwarding is now guarded by isClosed/hasListener checks. However, the commit also contains suspicious development artifacts: periodic_tasks.dart increments currentTxCount by 1 with a ‘FIXME: REMOVE THIS +1 WHEN DONE’ comment, and wallet_home.dart calls NotificationService().showIncomingTxNotification(1) unconditionally in initState. The MoneroWallet.framework binary is added without source or reproducible build info, and its code signature data is only a generic CodeResources template.
Changed components
ios/Frameworks/MoneroWallet.frameworkios/Runner.xcodeprojlib/main.dartlib/periodic_tasks.dartlib/screens/wallet_home.dartlib/screens/settings.dartlib/services/notifications_service.dartlib/services/tor_service.dartlib/services/tor_settings_service.dartlib/util/socks_http.dartlib/util/socks_socket.dartlib/util/logging.dartInspect captured patch +853 / −118
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 7c56964..1dc6cf7 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
- <string>12.0</string>
+ <string>13.0</string>
</dict>
</plist>
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/Frameworks/MoneroWallet.framework/Info.plist b/ios/Frameworks/MoneroWallet.framework/Info.plist
new file mode 100644
index 0000000..eff78ac
--- /dev/null
+++ b/ios/Frameworks/MoneroWallet.framework/Info.plist
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleExecutable</key>
+ <string>MoneroWallet</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.skylight.MoneroWallet</string>
+ <key>CFBundleName</key>
+ <string>MoneroWallet</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>UIBackgroundModes</key>
+ <array>
+ <string>fetch</string>
+ </array>
+</dict>
+</plist>
\ No newline at end of file
diff --git a/ios/Frameworks/MoneroWallet.framework/MoneroWallet b/ios/Frameworks/MoneroWallet.framework/MoneroWallet
new file mode 100755
index 0000000..1bad79c
Binary files /dev/null and b/ios/Frameworks/MoneroWallet.framework/MoneroWallet differ
diff --git a/ios/Frameworks/MoneroWallet.framework/_CodeSignature/CodeResources b/ios/Frameworks/MoneroWallet.framework/_CodeSignature/CodeResources
new file mode 100644
index 0000000..9bfb14b
--- /dev/null
+++ b/ios/Frameworks/MoneroWallet.framework/_CodeSignature/CodeResources
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Info.plist</key>
+ <data>
+ RrK6+FmVdJY+1UKmkipQpIlKocw=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict/>
+ <key>rules</key>
+ <dict>
+ <key>^.*</key>
+ <true/>
+ <key>^.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^Base\.lproj/</key>
+ <dict>
+ <key>weight</key>
+ <real>1010</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^Base\.lproj/</key>
+ <dict>
+ <key>weight</key>
+ <real>1010</real>
+ </dict>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..3076416
--- /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..17b6142
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,92 @@
+PODS:
+ - Flutter (1.0.0)
+ - flutter_local_notifications (0.0.1):
+ - Flutter
+ - flutter_secure_storage (6.0.0):
+ - Flutter
+ - local_auth_darwin (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - mobile_scanner (7.0.0):
+ - Flutter
+ - FlutterMacOS
+ - package_info_plus (0.4.5):
+ - Flutter
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - permission_handler_apple (9.3.0):
+ - Flutter
+ - screen_brightness_ios (0.1.0):
+ - Flutter
+ - share_plus (0.0.1):
+ - Flutter
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - tor_ffi_plugin (0.0.1):
+ - Flutter
+ - workmanager_apple (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
+ - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
+ - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
+ - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
+ - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
+ - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
+ - share_plus (from `.symlinks/plugins/share_plus/ios`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+ - tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
+ - workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ flutter_local_notifications:
+ :path: ".symlinks/plugins/flutter_local_notifications/ios"
+ flutter_secure_storage:
+ :path: ".symlinks/plugins/flutter_secure_storage/ios"
+ local_auth_darwin:
+ :path: ".symlinks/plugins/local_auth_darwin/darwin"
+ mobile_scanner:
+ :path: ".symlinks/plugins/mobile_scanner/darwin"
+ package_info_plus:
+ :path: ".symlinks/plugins/package_info_plus/ios"
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
+ permission_handler_apple:
+ :path: ".symlinks/plugins/permission_handler_apple/ios"
+ screen_brightness_ios:
+ :path: ".symlinks/plugins/screen_brightness_ios/ios"
+ share_plus:
+ :path: ".symlinks/plugins/share_plus/ios"
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ tor_ffi_plugin:
+ :path: ".symlinks/plugins/tor_ffi_plugin/ios"
+ workmanager_apple:
+ :path: ".symlinks/plugins/workmanager_apple/ios"
+
+SPEC CHECKSUMS:
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
+ flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
+ local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
+ mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
+ package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
+ path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
+ permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
+ screen_brightness_ios: 9953fd7da5bd480f1a93990daeec2eb42d4f3b52
+ share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
+ shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
+ tor_ffi_plugin: 2a4dd758515e5124dce8323f950f6bb135e611b5
+ workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
+
+PODFILE CHECKSUM: 3d68f7cb47d5f2fb7765407f663653c9b51100f3
+
+COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index a988bc6..120a73f 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -10,10 +10,14 @@
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 */; };
+ 6F680F332F0C235200E6DE71 /* MoneroWallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F680F312F0C231F00E6DE71 /* MoneroWallet.framework */; };
+ 6F680F342F0C235200E6DE71 /* MoneroWallet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6F680F312F0C231F00E6DE71 /* MoneroWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 79D78220F5B529E1F8AAEC80 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5801F2E4C06C47481468529E /* Pods_Runner.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 */; };
+ D587F92C1947DB9F625B3F24 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A82A71B64192DCF8A518EB3 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -33,6 +37,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
+ 6F680F342F0C235200E6DE71 /* MoneroWallet.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -42,9 +47,15 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+ 1E186A242ACE0EB38B7C86CC /* 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 = "<group>"; };
+ 270E0AF39FBB7A6C0A6AA2A4 /* 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 = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3344664AE9D4D6E25C3F7E1B /* 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 = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+ 5801F2E4C06C47481468529E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 5921F250118D12D155280500 /* 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 = "<group>"; };
+ 6F680F312F0C231F00E6DE71 /* MoneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MoneroWallet.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -55,19 +66,42 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ 9A82A71B64192DCF8A518EB3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ E3DD4CA775711C6C8B864A0A /* 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 = "<group>"; };
+ F9F011F3833A8F336C647460 /* 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 = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 6F455EC221F38059335097B3 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D587F92C1947DB9F625B3F24 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 79D78220F5B529E1F8AAEC80 /* Pods_Runner.framework in Frameworks */,
+ 6F680F332F0C235200E6DE71 /* MoneroWallet.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 238254620A1D6CBD8992FC9E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6F680F322F0C231F00E6DE71 /* Frameworks */,
+ 5801F2E4C06C47481468529E /* Pods_Runner.framework */,
+ 9A82A71B64192DCF8A518EB3 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
@@ -76,6 +110,14 @@
path = RunnerTests;
sourceTree = "<group>";
};
+ 6F680F322F0C231F00E6DE71 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6F680F312F0C231F00E6DE71 /* MoneroWallet.framework */,
+ );
+ path = Frameworks;
+ sourceTree = "<group>";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -94,6 +136,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+ FF3003167E283A5568BCD5E2 /* Pods */,
+ 238254620A1D6CBD8992FC9E /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -121,6 +165,19 @@
path = Runner;
sourceTree = "<group>";
};
+ FF3003167E283A5568BCD5E2 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 1E186A242ACE0EB38B7C86CC /* Pods-Runner.debug.xcconfig */,
+ 3344664AE9D4D6E25C3F7E1B /* Pods-Runner.release.xcconfig */,
+ 5921F250118D12D155280500 /* Pods-Runner.profile.xcconfig */,
+ 270E0AF39FBB7A6C0A6AA2A4 /* Pods-RunnerTests.debug.xcconfig */,
+ E3DD4CA775711C6C8B864A0A /* Pods-RunnerTests.release.xcconfig */,
+ F9F011F3833A8F336C647460 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -128,8 +185,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+ EA51133A99105CA637677868 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+ 6F455EC221F38059335097B3 /* Frameworks */,
);
buildRules = (
);
@@ -145,12 +204,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ BE5DF59B2E8B5315DD66BC99 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ FA400AF07F45AC9B9BB0A29C /* [CP] Embed Pods Frameworks */,
+ B05AE4C7A964EACCD3805024 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -253,6 +315,84 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ B05AE4C7A964EACCD3805024 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ BE5DF59B2E8B5315DD66BC99 /* [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;
+ };
+ EA51133A99105CA637677868 /* [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;
+ };
+ FA400AF07F45AC9B9BB0A29C /* [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;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -346,7 +486,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -363,7 +503,13 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Frameworks",
+ );
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Skylight Wallet";
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -378,6 +524,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 270E0AF39FBB7A6C0A6AA2A4 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -395,6 +542,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = E3DD4CA775711C6C8B864A0A /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -410,6 +558,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = F9F011F3833A8F336C647460 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -472,7 +621,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -523,7 +672,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -542,7 +691,13 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Frameworks",
+ );
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Skylight Wallet";
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -564,7 +719,13 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Frameworks",
+ );
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Skylight Wallet";
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
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 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
+ <FileRef
+ location = "group:Pods/Pods.xcodeproj">
+ </FileRef>
</Workspace>
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 6266644..4f95559 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -7,6 +7,10 @@ import UIKit
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
+ if #available(iOS 10.0, *) {
+ UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
+ }
+
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 9d9e97a..0234462 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -2,10 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+ <key>CADisableMinimumFrameDurationOnPhone</key>
+ <true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
- <string>Monero Light Wallet</string>
+ <string>Skylight Wallet</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -24,6 +26,12 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
+ <key>UIApplicationSupportsIndirectInputEvents</key>
+ <true/>
+ <key>UIBackgroundModes</key>
+ <array>
+ <string>fetch</string>
+ </array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -41,9 +49,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
- <key>CADisableMinimumFrameDurationOnPhone</key>
- <true/>
- <key>UIApplicationSupportsIndirectInputEvents</key>
- <true/>
</dict>
</plist>
diff --git a/ios/build/.last_build_id b/ios/build/.last_build_id
new file mode 100644
index 0000000..56f2db6
--- /dev/null
+++ b/ios/build/.last_build_id
@@ -0,0 +1 @@
+c134254776a5ccd144c8a5e627d78c10
\ No newline at end of file
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index b887791..8d3e701 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -89,6 +89,10 @@
"settingsAppLockUnableToAuthError": "Unable to authenticate. Make sure you have device unlock set up.",
"settingsVerboseLoggingLabel": "Verbose Logging",
"settingsVerboseLoggingDescription": "Logs wallet operations to a text file in the app's data folder for debugging purposes.",
+ "settingsVerboseLoggingDescriptionIos": "Logs wallet operations and allows the logs to be exported to a text file.",
+ "settingsExportLogsLabel": "Export Logs",
+ "settingsExportLogsButton": "Export",
+ "settingsExportLogsError": "No logs found to export.",
"settingsThemeLabel": "Theme",
"settingsThemeSystem": "System",
"settingsThemeLight": "Light",
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 4afe070..1b81381 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -627,6 +627,30 @@ abstract class AppLocalizations {
/// **'Logs wallet operations to a text file in the app\'s data folder for debugging purposes.'**
String get settingsVerboseLoggingDescription;
+ /// No description provided for @settingsVerboseLoggingDescriptionIos.
+ ///
+ /// In en, this message translates to:
+ /// **'Logs wallet operations and allows the logs to be exported to a text file.'**
+ String get settingsVerboseLoggingDescriptionIos;
+
+ /// No description provided for @settingsExportLogsLabel.
+ ///
+ /// In en, this message translates to:
+ /// **'Export Logs'**
+ String get settingsExportLogsLabel;
+
+ /// No description provided for @settingsExportLogsButton.
+ ///
+ /// In en, this message translates to:
+ /// **'Export'**
+ String get settingsExportLogsButton;
+
+ /// No description provided for @settingsExportLogsError.
+ ///
+ /// In en, this message translates to:
+ /// **'No logs found to export.'**
+ String get settingsExportLogsError;
+
/// No description provided for @settingsThemeLabel.
///
/// In en, this message translates to:
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 9b21780..0e205f3 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -288,6 +288,19 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsVerboseLoggingDescription =>
'Logs wallet operations to a text file in the app\'s data folder for debugging purposes.';
+ @override
+ String get settingsVerboseLoggingDescriptionIos =>
+ 'Logs wallet operations and allows the logs to be exported to a text file.';
+
+ @override
+ String get settingsExportLogsLabel => 'Export Logs';
+
+ @override
+ String get settingsExportLogsButton => 'Export';
+
+ @override
+ String get settingsExportLogsError => 'No logs found to export.';
+
@override
String get settingsThemeLabel => 'Theme';
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 286d978..04a21bc 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -288,6 +288,19 @@ class AppLocalizationsPt extends AppLocalizations {
String get settingsVerboseLoggingDescription =>
'Registra operações da carteira em um arquivo de texto na pasta de dados do app para fins de depuração.';
+ @override
+ String get settingsVerboseLoggingDescriptionIos =>
+ 'Registra operações da carteira e permite exportar os logs para um arquivo de texto.';
+
+ @override
+ String get settingsExportLogsLabel => 'Exportar Logs';
+
+ @override
+ String get settingsExportLogsButton => 'Exportar';
+
+ @override
+ String get settingsExportLogsError => 'Nenhum log encontrado para exportar.';
+
@override
String get settingsThemeLabel => 'Tema';
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 6aad530..2ba4198 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -89,6 +89,10 @@
"settingsAppLockUnableToAuthError": "Não foi possível autenticar. Verifique se o desbloqueio de tela está configurado.",
"settingsVerboseLoggingLabel": "Logs Detalhados",
"settingsVerboseLoggingDescription": "Registra operações da carteira em um arquivo de texto na pasta de dados do app para fins de depuração.",
+ "settingsVerboseLoggingDescriptionIos": "Registra operações da carteira e permite exportar os logs para um arquivo de texto.",
+ "settingsExportLogsLabel": "Exportar Logs",
+ "settingsExportLogsButton": "Exportar",
+ "settingsExportLogsError": "Nenhum log encontrado para exportar.",
"settingsThemeLabel": "Tema",
"settingsThemeSystem": "Sistema",
"settingsThemeLight": "Claro",
diff --git a/lib/main.dart b/lib/main.dart
index 7641509..09b90ba 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,5 +1,7 @@
+import 'dart:async';
import 'dart:io';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -46,16 +48,36 @@ final isDesktop = Platform.isLinux || Platform.isWindows || Platform.isMacOS;
final isMobile = Platform.isAndroid || Platform.isIOS;
void main() async {
- WidgetsFlutterBinding.ensureInitialized();
- timeago.setLocaleMessages('pt', timeago.PtBrMessages());
- await createAppDir();
- copyCacertToAppDocumentsDir();
- TorService.sharedInstance.start();
- TorSettingsService.sharedInstance.loadSettings();
- await registerPeriodicTasks();
- await NotificationService().init();
- cleanOldLogFiles();
- runApp(MyApp());
+ // Catch all uncaught async errors
+ runZonedGuarded(
+ () async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ // Catch Flutter framework errors
+ FlutterError.onError = (FlutterErrorDetails details) {
+ log(LogLevel.error, 'Flutter error: ${details.exception}');
+ if (kDebugMode) {
+ FlutterError.dumpErrorToConsole(details);
+ }
+ };
+
+ timeago.setLocaleMessages('pt', timeago.PtBrMessages());
+ await createAppDir();
+ copyCacertToAppDocumentsDir();
+ await registerPeriodicTasks();
+ await NotificationService().init();
+ cleanOldLogFiles();
+ runApp(MyApp());
+ },
+ (error, stackTrace) {
+ // Handle uncaught async errors (like socket disconnections)
+ log(LogLevel.error, 'Uncaught error: $error');
+ if (kDebugMode) {
+ debugPrint('Uncaught error: $error');
+ debugPrint('Stack trace: $stackTrace');
+ }
+ },
+ );
}
Future<bool> loadExistingWalletIfExists(WalletModel wallet) async {
@@ -99,6 +121,9 @@ class MyApp extends StatelessWidget {
]),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) {
+ TorSettingsService.sharedInstance.loadSettings();
+ TorService.sharedInstance.start();
+
final sharedPreferences = snapshot.data![0] as SharedPreferences;
final walletExists = snapshot.data![1] as bool;
diff --git a/lib/models/fiat_rate_model.dart b/lib/models/fiat_rate_model.dart
index 5a1a4a8..8898ce8 100644
--- a/lib/models/fiat_rate_model.dart
+++ b/lib/models/fiat_rate_model.dart
@@ -69,16 +69,20 @@ class FiatRateModel with ChangeNotifier {
throw Exception('Not fetching rate from fiat API because Tor is disabled');
}
- final response = await makeSocksHttpRequest('GET', url, proxyInfo);
-
- if (response.statusCode == 200) {
- final rate = response.jsonBody['result']?[pair]?['o'];
- if (rate is! String) {
- throw Exception('Could not find rate for $pair');
+ try {
+ final response = await makeSocksHttpRequest('GET', url, proxyInfo);
+ if (response.statusCode == 200) {
+ final rate = response.jsonBody['result']?[pair]?['o'];
+ if (rate is! String) {
+ throw Exception('Could not find rate for $pair');
+ }
+ return double.parse(rate);
+ } else {
+ throw Exception('Status code: ${response.statusCode}');
}
- return double.parse(rate);
- } else {
- throw Exception('Status code: ${response.statusCode}');
+ } catch (error) {
+ log(LogLevel.error, 'Failed to get fiat rate. ${error.toString()}');
+ throw Exception('Failed to get fiat rate. ${error.toString()}');
}
}
diff --git a/lib/periodic_tasks.dart b/lib/periodic_tasks.dart
index 42a75b4..9993c62 100644
--- a/lib/periodic_tasks.dart
+++ b/lib/periodic_tasks.dart
@@ -13,6 +13,7 @@ class PeriodicTasks {
}
Future<bool> runTxNotifier() async {
+ print('Running tx notifier');
final wallet = WalletModel();
if (!await wallet.hasExistingWallet()) {
@@ -26,8 +27,7 @@ Future<bool> runTxNotifier() async {
await TorService.sharedInstance.start();
await TorService.sharedInstance.waitUntilConnected().timeout(
Duration(minutes: 2),
- onTimeout: () =>
- log(LogLevel.warn, '[TX Notifier] Tor connection timed out'),
+ onTimeout: () => log(LogLevel.warn, '[TX Notifier] Tor connection timed out'),
);
}
@@ -53,7 +53,8 @@ Future<bool> runTxNotifier() async {
}
final persistedTxCount = await wallet.getPersistedTxHistoryCount();
- final currentTxCount = wallet.txHistory.length;
+ // FIXME: REMOVE THIS +1 WHEN DONE
+ final currentTxCount = wallet.txHistory.length + 1;
final countOfNewTxs = currentTxCount - persistedTxCount;
if (countOfNewTxs > 0 && currentTxCount != 0) {
@@ -91,23 +92,16 @@ void _callbackDispatcher() {
Future<void> registerTxNotifierTaskIfAllowed() async {
final notificationsEnabled =
- await SharedPreferencesService.get<bool>(
- SharedPreferencesKeys.notificationsEnabled,
- ) ??
- false;
+ await SharedPreferencesService.get<bool>(SharedPreferencesKeys.notificationsEnabled) ?? false;
if (!notificationsEnabled) {
return;
}
- final notificationsAreAllowed = await NotificationService()
- .promptPermission();
+ final notificationsAreAllowed = await NotificationService().promptPermission();
if (!notificationsAreAllowed) {
- await SharedPreferencesService.set<bool>(
- SharedPreferencesKeys.notificationsEnabled,
- false,
- );
+ await SharedPreferencesService.set<bool>(SharedPreferencesKeys.notificationsEnabled, false);
return;
}
@@ -118,10 +112,7 @@ Future<void> registerTxNotifierTaskIfAllowed() async {
"New transactions check",
PeriodicTasks.txNotifier,
frequency: Duration(minutes: 15),
- constraints: Constraints(
- networkType: NetworkType.connected,
- requiresBatteryNotLow: true,
- ),
+ constraints: Constraints(networkType: NetworkType.connected, requiresBatteryNotLow: true),
);
}
@@ -134,6 +125,6 @@ Future<void> registerPeriodicTasks() async {
return;
}
- Workmanager().initialize(_callbackDispatcher, isInDebugMode: true);
+ Workmanager().initialize(_callbackDispatcher);
await registerTxNotifierTaskIfAllowed();
}
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index 6967d4f..4577792 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -137,6 +137,73 @@ class _SettingsScreenState extends State<SettingsScreen> {
await SharedPreferencesService.set<bool>(SharedPreferencesKeys.verboseLoggingEnabled, value);
}
+ void _exportLogs() async {
+ final i18n = AppLocalizations.of(context)!;
+
+ try {
+ final logFiles = await getLogFiles();
+
+ if (logFiles.isEmpty) {
+ if (mounted) {
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text(i18n.settingsExportLogsError)));
+ }
+ return;
+ }
+
+ if (mounted) {
+ _showExportLogsDialog(logFiles);
+ }
+ } catch (e) {
+ if (mounted) {
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text(i18n.settingsExportLogsError)));
+ }
+ }
+ }
+
+ void _showExportLogsDialog(List<LogFileInfo> logFiles) {
+ final i18n = AppLocalizations.of(context)!;
+ final screenWidth = MediaQuery.of(context).size.width;
+ final dialogWidth = screenWidth.clamp(0.0, 500.0);
+
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ constraints: BoxConstraints.tightFor(width: dialogWidth),
+ insetPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
+ title: Text(i18n.settingsExportLogsLabel),
+ content: SizedBox(
+ width: double.maxFinite,
+ height: 300,
+ child: ListView.builder(
+ itemCount: logFiles.length,
+ itemBuilder: (context, index) {
+ final file = logFiles[index];
+ final dateStr =
+ '${file.modified.year}-${file.modified.month.toString().padLeft(2, '0')}-${file.modified.day.toString().padLeft(2, '0')}';
+ final sizeKb = (file.size / 1024).toStringAsFixed(1);
+
+ return ListTile(
+ onTap: () async {
+ Navigator.pop(context);
+ await exportLogFiles([file]);
+ },
+ leading: Icon(Icons.description_outlined),
+ title: Text(file.name),
+ subtitle: Text('$dateStr • $sizeKb KB'),
+ trailing: Icon(Icons.ios_share),
+ );
+ },
+ ),
+ ),
+ actions: [TextButton(onPressed: () => Navigator.pop(context), child: Text(i18n.cancel))],
+ ),
+ );
+ }
+
void _setFiatCurrency(String? value) async {
if (value == null) return;
@@ -427,7 +494,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
Text(i18n.settingsVerboseLoggingLabel, style: TextStyle(fontSize: 18)),
Text(
- i18n.settingsVerboseLoggingDescription,
+ Platform.isIOS
+ ? i18n.settingsVerboseLoggingDescriptionIos
+ : i18n.settingsVerboseLoggingDescription,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
@@ -436,6 +505,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
Switch(value: _verboseLoggingEnabled, onChanged: _setVerboseLoggingEnabled),
],
),
+ if (Platform.isIOS)
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(i18n.settingsExportLogsLabel, style: TextStyle(fontSize: 18)),
+ TextButton.icon(
+ onPressed: _verboseLoggingEnabled ? _exportLogs : null,
+ icon: Icon(Icons.ios_share),
+ label: Text(i18n.settingsExportLogsButton),
+ ),
+ ],
+ ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
diff --git a/lib/screens/wallet_home.dart b/lib/screens/wallet_home.dart
index 5550da1..fa84e68 100644
--- a/lib/screens/wallet_home.dart
+++ b/lib/screens/wallet_home.dart
@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:skylight_wallet/models/fiat_rate_model.dart';
+import 'package:skylight_wallet/services/notifications_service.dart';
import 'package:skylight_wallet/services/tor_service.dart';
import 'package:skylight_wallet/widgets/fiat_amount.dart';
import 'package:skylight_wallet/widgets/monero_amount.dart';
@@ -161,6 +162,8 @@ class _WalletHomeScreenState extends State<WalletHomeScreen> {
void initState() {
super.initState();
+ NotificationService().showIncomingTxNotification(1);
+
WidgetsBinding.instance.addPostFrameCallback((_) {
final Map<String, dynamic>? args =
ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
diff --git a/lib/services/notifications_service.dart b/lib/services/notifications_service.dart
index e7305a1..c712afc 100644
--- a/lib/services/notifications_service.dart
+++ b/lib/services/notifications_service.dart
@@ -1,20 +1,22 @@
+import 'dart:io';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-import 'package:permission_handler/permission_handler.dart';
class NotificationService {
final notificationsPlugin = FlutterLocalNotificationsPlugin();
Future<void> init() async {
- const initSettingsAndroid = AndroidInitializationSettings(
- '@mipmap/ic_launcher',
- );
-
- const initSettingsLinux = LinuxInitializationSettings(
- defaultActionName: 'Open wallet',
+ const initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
+ const initSettingsIOS = DarwinInitializationSettings(
+ // We'll request permissions manually
+ requestAlertPermission: false,
+ requestBadgePermission: false,
+ requestSoundPermission: false,
);
+ const initSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open wallet');
const initSettings = InitializationSettings(
android: initSettingsAndroid,
+ iOS: initSettingsIOS,
linux: initSettingsLinux,
);
@@ -22,28 +24,41 @@ class NotificationService {
}
Future<bool> promptPermission() async {
- final status = await Permission.notification.status;
+ if (Platform.isIOS) {
+ // Use flutter_local_notifications' iOS-specific permission request
+ final iosPlugin = notificationsPlugin
+ .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
+
+ final granted = await iosPlugin?.requestPermissions(alert: true, badge: true, sound: true);
- if (!status.isGranted) {
- final result = await Permission.notification.request();
- return result.isGranted;
+ return granted ?? false;
+ } else if (Platform.isAndroid) {
+ // Android 13+ needs runtime permission
+ final androidPlugin = notificationsPlugin
+ .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
+
+ final granted = await androidPlugin?.requestNotificationsPermission();
+ return granted ?? true; // Older Android versions don't need permission
}
- return true;
+ return true; // Other platforms (Linux, etc.)
}
Future<void> showIncomingTxNotification(double amountReceived) async {
+ const notificationChannelId = 'incoming_transactions';
+
await notificationsPlugin.show(
0,
'Incoming transaction',
'You received $amountReceived XMR',
const NotificationDetails(
android: AndroidNotificationDetails(
- 'incoming_transactions',
+ notificationChannelId,
'Transactions',
importance: Importance.max,
priority: Priority.high,
),
+ iOS: DarwinNotificationDetails(threadIdentifier: notificationChannelId),
),
);
}
diff --git a/lib/services/tor_service.dart b/lib/services/tor_service.dart
index 647817a..a7c2521 100644
--- a/lib/services/tor_service.dart
+++ b/lib/services/tor_service.dart
@@ -82,6 +82,7 @@ class TorService {
final completer = Completer<void>();
Timer.periodic(Duration(milliseconds: 50), (timer) {
+ print('waitUntilConnected: ${status.name}');
if (status == TorConnectionStatus.connected) {
timer.cancel();
completer.complete();
diff --git a/lib/services/tor_settings_service.dart b/lib/services/tor_settings_service.dart
index 6d51c20..25945f2 100644
--- a/lib/services/tor_settings_service.dart
+++ b/lib/services/tor_settings_service.dart
@@ -63,10 +63,15 @@ class TorSettingsService {
Future<({InternetAddress host, int port})?> getProxy() async {
if (_torMode == TorMode.builtIn) {
await TorService.sharedInstance.waitUntilConnected();
+ print('getProxyInfo builtIn: ${TorService.sharedInstance.getProxyInfo()}');
return TorService.sharedInstance.getProxyInfo();
} else if (_torMode == TorMode.external) {
+ print(
+ 'getProxyInfo external: (host: InternetAddress.loopbackIPv4, port: ${int.parse(_socksPort)})',
+ );
return (host: InternetAddress.loopbackIPv4, port: int.parse(_socksPort));
} else {
+ print('getProxyInfo disabled: null');
return null;
}
}
diff --git a/lib/util/logging.dart b/lib/util/logging.dart
index ad34d81..521941c 100644
--- a/lib/util/logging.dart
+++ b/lib/util/logging.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
+import 'package:share_plus/share_plus.dart';
import 'package:skylight_wallet/services/shared_preferences_service.dart';
import 'package:skylight_wallet/util/dirs.dart';
@@ -53,8 +54,6 @@ Future<void> cleanOldLogFiles() async {
if (Platform.isAndroid) {
directory = await getExternalStorageDirectory();
- } else if (Platform.isIOS) {
- directory = await getAppDir();
} else {
directory = await getAppDir();
}
@@ -126,3 +125,64 @@ Future<void> log(LogLevel level, String message, [Map<String, dynamic>? meta]) a
});
}
}
+
+class LogFileInfo {
+ final String path;
+ final String name;
+ final DateTime modified;
+ final int size;
+
+ LogFileInfo({required this.path, required this.name, required this.modified, required this.size});
+}
+
+/// Returns a list of available log files sorted by date (newest first)
+Future<List<LogFileInfo>> getLogFiles() async {
+ Directory? directory;
+
+ if (Platform.isAndroid) {
+ directory = await getExternalStorageDirectory();
+ } else {
+ directory = await getAppDir();
+ }
+
+ if (directory == null) {
+ return [];
+ }
+
+ final logsDir = Directory('${directory.path}/logs');
+ if (!await logsDir.exists()) {
+ return [];
+ }
+
+ final files = await logsDir.list().toList();
+ final logFiles = <LogFileInfo>[];
+
+ for (var entity in files) {
+ if (entity is File && entity.path.endsWith('.txt')) {
+ final stat = await entity.stat();
+ final name = entity.path.split('/').last;
+ logFiles.add(
+ LogFileInfo(path: entity.path, name: name, modified: stat.modified, size: stat.size),
+ );
+ }
+ }
+
+ // Sort by date (newest first)
+ logFiles.sort((a, b) => b.modified.compareTo(a.modified));
+ return logFiles;
+}
+
+/// Exports selected log files via the system share sheet (iOS/Android)
+Future<void> exportLogFiles(List<LogFileInfo> files) async {
+ if (files.isEmpty) {
+ throw Exception('No log files selected');
+ }
+
+ try {
+ final xFiles = files.map((f) => XFile(f.path)).toList();
+ await SharePlus.instance.share(ShareParams(files: xFiles));
+ } catch (error) {
+ debugPrint('Failed to export logs: $error');
+ rethrow;
+ }
+}
diff --git a/lib/util/socks_http.dart b/lib/util/socks_http.dart
index 47cfb27..9af295d 100644
--- a/lib/util/socks_http.dart
+++ b/lib/util/socks_http.dart
@@ -1,6 +1,8 @@
+import 'dart:async';
import 'dart:convert';
import 'dart:io';
+import 'package:flutter/foundation.dart';
import 'package:skylight_wallet/util/socks_socket.dart';
class ParsedHttpResponse {
@@ -121,19 +123,33 @@ Future<ParsedHttpResponse> makeSocksHttpRequest(
Object? body,
}) async {
final uri = Uri.parse(url);
-
- final socket = await SOCKSSocket.create(
- proxyHost: proxyInfo.host.address,
- proxyPort: proxyInfo.port,
- sslEnabled: uri.scheme == 'https',
- );
-
- await socket.connect();
- await socket.connectTo(uri.host, uri.port);
-
- final rawRequest = getRawHttpRequestString(method, url, jsonBody: body);
- final rawResponse = await socket.send(rawRequest);
- final parsedResponse = parseHttpResponse(rawResponse);
-
- return parsedResponse;
+ SOCKSSocket? socket;
+
+ try {
+ socket = await SOCKSSocket.create(
+ proxyHost: proxyInfo.host.address,
+ proxyPort: proxyInfo.port,
+ sslEnabled: uri.scheme == 'https',
+ );
+
+ await socket.connect();
+ await socket.connectTo(uri.host, uri.port);
+
+ final rawRequest = getRawHttpRequestString(method, url, jsonBody: body);
+ final rawResponse = await socket.send(rawRequest);
+ final parsedResponse = parseHttpResponse(rawResponse);
+
+ return parsedResponse;
+ } catch (e) {
+ debugPrint('makeSocksHttpRequest error: $e');
+ rethrow;
+ } finally {
+ // Always close the socket when done
+ try {
+ await socket?.close();
+ } catch (e) {
+ // Ignore errors when closing - socket may already be closed
+ debugPrint('Error closing socket: $e');
+ }
+ }
}
diff --git a/lib/util/socks_socket.dart b/lib/util/socks_socket.dart
index b3ec28b..84742df 100644
--- a/lib/util/socks_socket.dart
+++ b/lib/util/socks_socket.dart
@@ -68,12 +68,10 @@ class SOCKSSocket {
late final Socket _secureSocksSocket;
/// A StreamController that listens to the _socksSocket and broadcasts.
- final StreamController<List<int>> _responseController =
- StreamController.broadcast();
+ late final StreamController<List<int>> _responseController;
/// A StreamController that listens to the _secureSocksSocket and broadcasts.
- final StreamController<List<int>> _secureResponseController =
- StreamController.broadcast();
+ late final StreamController<List<int>> _secureResponseController;
/// Getter for the StreamController that listens to the _socksSocket and
/// broadcasts, or the _secureSocksSocket and broadcasts if SSL is enabled.
@@ -92,12 +90,15 @@ class SOCKSSocket {
final bool sslEnabled;
/// Private constructor.
- SOCKSSocket._(this.proxyHost, this.proxyPort, this.sslEnabled);
+ SOCKSSocket._(this.proxyHost, this.proxyPort, this.sslEnabled) {
+ // Initialize stream controllers with error handling to prevent uncaught errors
+ _responseController = StreamController.broadcast(onListen: null, onCancel: null);
+ _secureResponseController = StreamController.broadcast(onListen: null, onCancel: null);
+ }
/// Provides a stream of data as List<int>.
- Stream<List<int>> get inputStream => sslEnabled
- ? _secureResponseController.stream
- : _responseController.stream;
+ Stream<List<int>> get inputStream =>
+ sslEnabled ? _secureResponseController.stream : _responseController.stream;
/// Provides a StreamSink compatible with List<int> for sending data.
StreamSink<List<int>> get outputStream {
@@ -141,11 +142,10 @@ class SOCKSSocket {
}
/// Constructor.
- SOCKSSocket({
- required this.proxyHost,
- required this.proxyPort,
- required this.sslEnabled,
- }) {
+ SOCKSSocket({required this.proxyHost, required this.proxyPort, required this.sslEnabled}) {
+ // Initialize stream controllers
+ _responseController = StreamController.broadcast();
+ _secureResponseController = StreamController.broadcast();
_init();
}
@@ -163,21 +163,21 @@ class SOCKSSocket {
_subscription = _socksSocket.listen(
(data) {
// Add the data to the response controller.
- _responseController.add(data);
+ if (!_responseController.isClosed) {
+ _responseController.add(data);
+ }
},
- onError: (e) {
- // Handle errors.
- if (e is Object) {
- _responseController.addError(e);
+ onError: (e, stackTrace) {
+ // Log the error for debugging
+ debugPrint('SOCKSSocket error: $e');
+ // Only forward error if controller is open and has listeners
+ if (!_responseController.isClosed && _responseController.hasListener) {
+ _responseController.addError(e, stackTrace);
}
-
- // If the error is not an object, send the error as a string.
- _responseController.addError("$e");
- // TODO make sure sending error as string is acceptable.
},
onDone: () {
- // Close the response controller when the socket is closed.
- // _responseController.close();
+ // Socket closed - don't close controller here as it may be reused
+ debugPrint('SOCKSSocket: connection closed');
},
);
}
@@ -195,9 +195,7 @@ class SOCKSSocket {
// Check if the connection was successful.
if (response[1] != 0x00) {
- throw Exception(
- 'socks_socket.connect(): Failed to connect to SOCKS5 proxy.',
- );
+ throw Exception('socks_socket.connect(): Failed to connect to SOCKS5 proxy.');
}
return;
@@ -250,21 +248,23 @@ class SOCKSSocket {
_subscription = _secureSocksSocket.listen(
(data) {
// Add the data to the response controller.
- _secureResponseController.add(data);
+ if (!_secureResponseController.isClosed) {
+ _secureResponseController.add(data);
+ }
},
- onError: (e) {
- // Handle errors.
- if (e is Object) {
- _secureResponseController.addError(e);
+ onError: (e, stackTrace) {
+ // Log the error for debugging
+ debugPrint('SOCKSSocket (secure) error: $e');
+ // Only forward error if controller is open and has listeners
+ if (!_secureResponseController.isClosed && _secureResponseController.hasListener) {
+ _secureResponseController.addError(e, stackTrace);
}
-
- // If the error is not an object, send the error as a string.
- _secureResponseController.addError("$e");
- // TODO make sure sending error as string is acceptable.
},
onDone: () {
// Close the response controller when the socket is closed.
- _secureResponseController.close();
+ if (!_secureResponseController.isClosed) {
+ _secureResponseController.close();
+ }
},
);
}
@@ -307,8 +307,10 @@ class SOCKSSocket {
} finally {
await _subscription?.cancel();
await _socksSocket.close();
- _responseController.close();
- if (sslEnabled) {
+ if (!_responseController.isClosed) {
+ _responseController.close();
+ }
+ if (sslEnabled && !_secureResponseController.isClosed) {
_secureResponseController.close();
}
}
@@ -344,8 +346,7 @@ class SOCKSSocket {
/// A Future that resolves to void.
Future<void> sendServerFeaturesCommand() async {
// The server.features command.
- const String command =
- '{"jsonrpc":"2.0","id":"0","method":"server.features","params":[]}';
+ const String command = '{"jsonrpc":"2.0","id":"0","method":"server.features","params":[]}';
if (!sslEnabled) {
// Send the command to the proxy server.
diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig
index c2efd0b..4b81f9b 100644
--- a/macos/Flutter/Flutter-Debug.xcconfig
+++ b/macos/Flutter/Flutter-Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig
index c2efd0b..5caa9d1 100644
--- a/macos/Flutter/Flutter-Release.xcconfig
+++ b/macos/Flutter/Flutter-Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/macos/Podfile b/macos/Podfile
new file mode 100644
index 0000000..ff5ddb3
--- /dev/null
+++ b/macos/Podfile
@@ -0,0 +1,42 @@
+platform :osx, '10.15'
+
+# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_macos_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_macos_build_settings(target)
+ end
+end
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.