Fix Flutter iOS build and add web support with web-safe storage (#878)

* Add Flutter web support and web-safe storage

* Update mobile/web/index.html

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Product name instead of placeholder

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Juan José Mata
2026-02-03 12:29:38 +01:00
committed by GitHub
parent 2df4406af3
commit 4be1c39e1f
17 changed files with 638 additions and 51 deletions

View File

@@ -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;

View File

@@ -6,11 +6,37 @@ import 'log_service.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
static final Map<String, Map<String, dynamic>> _memoryTransactions = {};
static final Map<String, Map<String, dynamic>> _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<Database> 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<String> insertTransaction(Map<String, dynamic> transaction) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final localId = transaction['local_id'] as String;
_memoryTransactions[localId] = Map<String, dynamic>.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<List<Map<String, dynamic>>> 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<String, dynamic>.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<Map<String, dynamic>?> getTransactionByLocalId(String localId) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final transaction = _memoryTransactions[localId];
return transaction != null ? Map<String, dynamic>.from(transaction) : null;
}
final db = await database;
final results = await db.query(
'transactions',
@@ -172,6 +225,15 @@ class DatabaseHelper {
}
Future<Map<String, dynamic>?> getTransactionByServerId(String serverId) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
for (final transaction in _memoryTransactions.values) {
if (transaction['server_id'] == serverId) {
return Map<String, dynamic>.from(transaction);
}
}
return null;
}
final db = await database;
final results = await db.query(
'transactions',
@@ -184,6 +246,17 @@ class DatabaseHelper {
}
Future<List<Map<String, dynamic>>> getPendingTransactions() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final results = _memoryTransactions.values
.where((transaction) => transaction['sync_status'] == 'pending')
.map((transaction) => Map<String, dynamic>.from(transaction))
.toList();
results.sort(
(a, b) => _compareAsc(a['created_at'] as String?, b['created_at'] as String?),
);
return results;
}
final db = await database;
return await db.query(
'transactions',
@@ -194,6 +267,17 @@ class DatabaseHelper {
}
Future<List<Map<String, dynamic>>> getPendingDeletes() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final results = _memoryTransactions.values
.where((transaction) => transaction['sync_status'] == 'pending_delete')
.map((transaction) => Map<String, dynamic>.from(transaction))
.toList();
results.sort(
(a, b) => _compareAsc(a['updated_at'] as String?, b['updated_at'] as String?),
);
return results;
}
final db = await database;
return await db.query(
'transactions',
@@ -204,6 +288,16 @@ class DatabaseHelper {
}
Future<int> updateTransaction(String localId, Map<String, dynamic> transaction) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
if (!_memoryTransactions.containsKey(localId)) {
return 0;
}
final updated = Map<String, dynamic>.from(transaction);
updated['local_id'] = localId;
_memoryTransactions[localId] = updated;
return 1;
}
final db = await database;
return await db.update(
'transactions',
@@ -214,6 +308,10 @@ class DatabaseHelper {
}
Future<int> deleteTransaction(String localId) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
return _memoryTransactions.remove(localId) != null ? 1 : 0;
}
final db = await database;
return await db.delete(
'transactions',
@@ -223,6 +321,19 @@ class DatabaseHelper {
}
Future<int> deleteTransactionByServerId(String serverId) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
String? localIdToRemove;
for (final entry in _memoryTransactions.entries) {
if (entry.value['server_id'] == serverId) {
localIdToRemove = entry.key;
break;
}
}
if (localIdToRemove == null) return 0;
_memoryTransactions.remove(localIdToRemove);
return 1;
}
final db = await database;
return await db.delete(
'transactions',
@@ -232,11 +343,27 @@ class DatabaseHelper {
}
Future<void> clearTransactions() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
_memoryTransactions.clear();
return;
}
final db = await database;
await db.delete('transactions');
}
Future<void> clearSyncedTransactions() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final keysToRemove = _memoryTransactions.entries
.where((entry) => entry.value['sync_status'] == 'synced')
.map((entry) => entry.key)
.toList();
for (final key in keysToRemove) {
_memoryTransactions.remove(key);
}
return;
}
final db = await database;
_log.debug('DatabaseHelper', 'Clearing only synced transactions, keeping pending/failed');
await db.delete(
@@ -248,6 +375,12 @@ class DatabaseHelper {
// Account CRUD operations (for caching)
Future<void> insertAccount(Map<String, dynamic> account) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final id = account['id'] as String;
_memoryAccounts[id] = Map<String, dynamic>.from(account);
return;
}
final db = await database;
await db.insert(
'accounts',
@@ -257,6 +390,14 @@ class DatabaseHelper {
}
Future<void> insertAccounts(List<Map<String, dynamic>> accounts) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
for (final account in accounts) {
final id = account['id'] as String;
_memoryAccounts[id] = Map<String, dynamic>.from(account);
}
return;
}
final db = await database;
final batch = db.batch();
@@ -272,11 +413,26 @@ class DatabaseHelper {
}
Future<List<Map<String, dynamic>>> getAccounts() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final results = _memoryAccounts.values
.map((account) => Map<String, dynamic>.from(account))
.toList();
results.sort(
(a, b) => _compareAsc(a['name'] as String?, b['name'] as String?),
);
return results;
}
final db = await database;
return await db.query('accounts', orderBy: 'name ASC');
}
Future<Map<String, dynamic>?> getAccountById(String id) async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
final account = _memoryAccounts[id];
return account != null ? Map<String, dynamic>.from(account) : null;
}
final db = await database;
final results = await db.query(
'accounts',
@@ -289,18 +445,35 @@ class DatabaseHelper {
}
Future<void> clearAccounts() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
_memoryAccounts.clear();
return;
}
final db = await database;
await db.delete('accounts');
}
// Utility methods
Future<void> clearAllData() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
_memoryTransactions.clear();
_memoryAccounts.clear();
return;
}
final db = await database;
await db.delete('transactions');
await db.delete('accounts');
}
Future<void> close() async {
if (_useInMemoryStore) {
_ensureWebStoreReady();
_memoryTransactions.clear();
_memoryAccounts.clear();
return;
}
if (_database != null) {
await _database!.close();
_database = null;