diff --git a/mobile/.gitignore b/mobile/.gitignore
index 39d59563b..c7e1da6e8 100644
--- a/mobile/.gitignore
+++ b/mobile/.gitignore
@@ -25,6 +25,7 @@ ios/Pods/
ios/.symlinks/
ios/Flutter/Flutter.framework
ios/Flutter/Flutter.podspec
+ios/Flutter/ephemeral/
ios/Flutter/Generated.xcconfig
ios/Runner.xcworkspace/
ios/Podfile.lock
diff --git a/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
new file mode 100644
index 000000000..5c9d77fc1
--- /dev/null
+++ b/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -0,0 +1,44 @@
+package io.flutter.plugins;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import io.flutter.Log;
+
+import io.flutter.embedding.engine.FlutterEngine;
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Android platform.
+ */
+@Keep
+public final class GeneratedPluginRegistrant {
+ private static final String TAG = "GeneratedPluginRegistrant";
+ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
+ try {
+ flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.connectivity.ConnectivityPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin connectivity_plus, dev.fluttercommunity.plus.connectivity.ConnectivityPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.tekartik.sqflite.SqflitePlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin sqflite_android, com.tekartik.sqflite.SqflitePlugin", e);
+ }
+ }
+}
diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/ios/Flutter/AppFrameworkInfo.plist
index 7c5696400..1dc6cf765 100644
--- a/mobile/ios/Flutter/AppFrameworkInfo.plist
+++ b/mobile/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj
index 030d8a5b5..649b91cd7 100644
--- a/mobile/ios/Runner.xcodeproj/project.pbxproj
+++ b/mobile/ios/Runner.xcodeproj/project.pbxproj
@@ -10,6 +10,7 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 8DDD6AE026E53835686F984E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECA0200469BC2067F6002294 /* 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 */; };
@@ -31,6 +32,7 @@
/* 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 = ""; };
+ 1DC166D4710E23F2EDEDA52D /* 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 = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
@@ -42,6 +44,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 = ""; };
+ BEEF4E3D580B721E5A0852CA /* 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 = ""; };
+ ECA0200469BC2067F6002294 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ FE30D351EF409EF61606DE07 /* 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 = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +54,32 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 8DDD6AE026E53835686F984E /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 10A9A853ACC5019AE46A5DDA /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ BEEF4E3D580B721E5A0852CA /* Pods-Runner.debug.xcconfig */,
+ FE30D351EF409EF61606DE07 /* Pods-Runner.release.xcconfig */,
+ 1DC166D4710E23F2EDEDA52D /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 4D2624EB4309F79988F7AC61 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ ECA0200469BC2067F6002294 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -72,6 +97,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
+ 10A9A853ACC5019AE46A5DDA /* Pods */,
+ 4D2624EB4309F79988F7AC61 /* Frameworks */,
);
sourceTree = "";
};
@@ -105,12 +132,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 17EEDD344E8596DDA68B7B10 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 0C5310326ADD4D85F74E6637 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -170,6 +199,45 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 0C5310326ADD4D85F74E6637 /* [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;
+ };
+ 17EEDD344E8596DDA68B7B10 /* [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;
@@ -278,7 +346,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 = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -357,7 +425,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 = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -408,7 +476,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 = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 000000000..9c12df59c
--- /dev/null
+++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/ios/Runner/GeneratedPluginRegistrant.h b/mobile/ios/Runner/GeneratedPluginRegistrant.h
index 3ab55947f..7a8909271 100644
--- a/mobile/ios/Runner/GeneratedPluginRegistrant.h
+++ b/mobile/ios/Runner/GeneratedPluginRegistrant.h
@@ -1,5 +1,19 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GeneratedPluginRegistrant_h
+#define GeneratedPluginRegistrant_h
+
#import
+NS_ASSUME_NONNULL_BEGIN
+
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject*)registry;
@end
+
+NS_ASSUME_NONNULL_END
+#endif /* GeneratedPluginRegistrant_h */
diff --git a/mobile/ios/Runner/GeneratedPluginRegistrant.m b/mobile/ios/Runner/GeneratedPluginRegistrant.m
index efe65eccc..f59edcc75 100644
--- a/mobile/ios/Runner/GeneratedPluginRegistrant.m
+++ b/mobile/ios/Runner/GeneratedPluginRegistrant.m
@@ -6,9 +6,44 @@
#import "GeneratedPluginRegistrant.h"
+#if __has_include()
+#import
+#else
+@import connectivity_plus;
+#endif
+
+#if __has_include()
+#import
+#else
+@import flutter_secure_storage_darwin;
+#endif
+
+#if __has_include()
+#import
+#else
+@import path_provider_foundation;
+#endif
+
+#if __has_include()
+#import
+#else
+@import shared_preferences_foundation;
+#endif
+
+#if __has_include()
+#import
+#else
+@import sqflite_darwin;
+#endif
+
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject*)registry {
+ [ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]];
+ [FlutterSecureStorageDarwinPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterSecureStorageDarwinPlugin"]];
+ [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];
+ [SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
+ [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]];
}
@end
diff --git a/mobile/lib/services/api_config.dart b/mobile/lib/services/api_config.dart
index a51c50219..bb5f353b0 100644
--- a/mobile/lib/services/api_config.dart
+++ b/mobile/lib/services/api_config.dart
@@ -5,7 +5,7 @@ class ApiConfig {
// For local development, use: http://10.0.2.2:3000 (Android emulator)
// For iOS simulator, use: http://localhost:3000
// For production, use your actual server URL
- static String _baseUrl = 'http://10.0.2.2:3000';
+ static String _baseUrl = 'https://app.sure.am';
static String get baseUrl => _baseUrl;
diff --git a/mobile/lib/services/database_helper.dart b/mobile/lib/services/database_helper.dart
index d483a034b..0ec4efc0f 100644
--- a/mobile/lib/services/database_helper.dart
+++ b/mobile/lib/services/database_helper.dart
@@ -6,11 +6,37 @@ import 'log_service.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
+ static final Map> _memoryTransactions = {};
+ static final Map> _memoryAccounts = {};
+ static bool _webStorageLogged = false;
final LogService _log = LogService.instance;
DatabaseHelper._init();
+ bool get _useInMemoryStore => kIsWeb;
+
+ void _ensureWebStoreReady() {
+ if (!_useInMemoryStore || _webStorageLogged) return;
+ _webStorageLogged = true;
+ _log.info(
+ 'DatabaseHelper',
+ 'Using in-memory storage on web (sqflite is not supported in browser builds).',
+ );
+ }
+
+ int _compareDesc(String? left, String? right) {
+ return (right ?? '').compareTo(left ?? '');
+ }
+
+ int _compareAsc(String? left, String? right) {
+ return (left ?? '').compareTo(right ?? '');
+ }
+
Future get database async {
+ if (_useInMemoryStore) {
+ _ensureWebStoreReady();
+ throw StateError('sqflite database is not available on web.');
+ }
if (_database != null) return _database!;
try {
@@ -124,6 +150,12 @@ class DatabaseHelper {
// Transaction CRUD operations
Future insertTransaction(Map transaction) async {
+ if (_useInMemoryStore) {
+ _ensureWebStoreReady();
+ final localId = transaction['local_id'] as String;
+ _memoryTransactions[localId] = Map.from(transaction);
+ return localId;
+ }
final db = await database;
_log.debug('DatabaseHelper', 'Inserting transaction: local_id=${transaction['local_id']}, account_id="${transaction['account_id']}", server_id=${transaction['server_id']}');
await db.insert(
@@ -136,6 +168,22 @@ class DatabaseHelper {
}
Future>> getTransactions({String? accountId}) async {
+ if (_useInMemoryStore) {
+ _ensureWebStoreReady();
+ final results = _memoryTransactions.values
+ .where((transaction) {
+ final storedAccountId = transaction['account_id'] as String?;
+ return accountId == null || storedAccountId == accountId;
+ })
+ .map((transaction) => Map.from(transaction))
+ .toList();
+ results.sort((a, b) {
+ final dateCompare = _compareDesc(a['date'] as String?, b['date'] as String?);
+ if (dateCompare != 0) return dateCompare;
+ return _compareDesc(a['created_at'] as String?, b['created_at'] as String?);
+ });
+ return results;
+ }
final db = await database;
if (accountId != null) {
@@ -160,6 +208,11 @@ class DatabaseHelper {
}
Future