import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod; import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/matrix.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart' as sqflite_native; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:pyramid/core/auth_log.dart'; import 'package:pyramid/core/soft_logout_guard.dart'; import 'package:pyramid/core/voip_manager.dart'; final matrixClientProvider = FutureProvider((ref) async { final DatabaseFactory factory; if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { // Use the native sqflite plugin — Android/iOS have system SQLite built-in, // no .so loading required. factory = sqflite_native.databaseFactory; } else if (!kIsWeb) { sqfliteFfiInit(); factory = databaseFactoryFfi; } else { throw UnsupportedError('Web not supported'); } final appDir = await getApplicationSupportDirectory(); final dbPath = p.join(appDir.path, 'pyramid.sqlite'); final db = await factory.openDatabase(dbPath); // Optimization for Windows/FFI to prevent "database is locked" errors try { await db.execute('PRAGMA journal_mode=WAL'); await db.execute('PRAGMA busy_timeout = 5000'); } catch (e) { if (kDebugMode) debugPrint('Could not set PRAGMAs: $e'); } final sdkDb = await MatrixSdkDatabase.init('pyramid', database: db); final client = Client( 'Pyramid', database: sdkDb, // Custom State-Events, die beim App-Start mit aus der DB geladen werden // müssen — sonst sind Space-Banner & Voice-Presence nach Neustart leer, // bis der jeweilige Raum vollständig nachgeladen wird. importantStateEvents: { 'io.pyramid.space.banner', 'io.pyramid.voice.presence', }, verificationMethods: { KeyVerificationMethod.numbers, KeyVerificationMethod.emoji, KeyVerificationMethod.qrShow, }, logLevel: kReleaseMode ? Level.warning : Level.verbose, // Ohne eigenen Handler eskaliert das SDK jeden fehlgeschlagenen // Token-Refresh (auch bloße Netzwerkfehler!) zu logout()+clear() — // Session und Krypto-Schlüssel wären weg. Siehe soft_logout_guard.dart. onSoftLogout: guardedSoftLogoutRefresh, nativeImplementations: NativeImplementationsIsolate( compute, vodozemacInit: () => vod.init(), ), ); // Persistiert Warnungen/Fehler des SDK sowie jeden Login-Status-Wechsel, // damit ein unerwarteter Logout (z. B. Soft-Logout vom Server) auch nach // einem App-Neustart noch nachvollziehbar ist (ROADMAP M1, Uta-Logout-Bug). Logs().onLog = (event) { if (event.level.index <= Level.warning.index) { unawaited(AuthLog.write('[SDK ${event.level.name}] ${event.toFormattedString()}')); } }; client.onLoginStateChanged.stream.listen((state) { unawaited(AuthLog.write('LoginState -> ${state.name}')); }); await client.init( waitForFirstSync: false, waitUntilLoadCompletedLoaded: false, ); // Re-use or create singleton instance try { PyramidVoipManager.instance.client; } catch (_) { PyramidVoipManager(client); } return client; }); final secureStorageProvider = Provider((ref) { return const FlutterSecureStorage(); });