From dcca2cbec012d4bea0b5a9749e765c874c495b4a Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 26 Apr 2026 02:10:16 +0200 Subject: [PATCH] bug fixes --- lib/app.dart | 85 ++++++------------- lib/main.dart | 5 +- lib/src/providers/connection.provider.dart | 4 +- lib/src/providers/purchases.provider.dart | 10 ++- lib/src/services/api.service.dart | 9 +- lib/src/services/user.service.dart | 2 +- .../share_image_contact_selection.view.dart | 57 ++++++++----- .../visual/views/chats/chat_list.view.dart | 2 +- 8 files changed, 82 insertions(+), 92 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 71ac5a04..73d95f9a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -7,10 +7,8 @@ import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; -import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; -import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/visual/components/app_outdated.comp.dart'; @@ -24,54 +22,33 @@ import 'package:twonly/src/visual/views/settings/backup/backup_setup.view.dart'; import 'package:twonly/src/visual/views/unlock_twonly.view.dart'; class App extends StatefulWidget { - const App({super.key}); + const App({required this.storageError, super.key}); + final bool storageError; @override State createState() => _AppState(); } class _AppState extends State with WidgetsBindingObserver { - bool wasPaused = false; - Object? _storageError; + bool _wasPaused = false; @override void initState() { super.initState(); AppState.isAppInBackground = false; WidgetsBinding.instance.addObserver(this); - - unawaited(initAsync()); - } - - Future initAsync() async { - try { - if (userService.isUserCreated && mounted) { - context.read().updatePlan( - planFromString(userService.currentUser.subscriptionPlan), - ); - } - } catch (e) { - Log.error('Storage error in App.initAsync: $e'); - if (mounted) { - setState(() { - _storageError = e; - }); - } - } - await apiService.connect(); - await apiService.listenToNetworkChanges(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { - if (wasPaused) { + if (_wasPaused) { AppState.isAppInBackground = false; twonlyDB.markUpdated(); unawaited(apiService.connect()); } } else if (state == AppLifecycleState.paused) { - wasPaused = true; + _wasPaused = true; AppState.isAppInBackground = true; } } @@ -85,7 +62,7 @@ class _AppState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { return ListenableBuilder( - listenable: context.watch(), + listenable: context.read(), builder: (context, child) { const localizationsDelegates = [ AppLocalizations.delegate, @@ -99,7 +76,7 @@ class _AppState extends State with WidgetsBindingObserver { Locale('de', ''), ]; - if (_storageError != null) { + if (widget.storageError) { return MaterialApp( scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey, localizationsDelegates: localizationsDelegates, @@ -108,7 +85,7 @@ class _AppState extends State with WidgetsBindingObserver { title: 'twonly', theme: lightTheme, darkTheme: darkTheme, - themeMode: context.watch().themeMode, + themeMode: context.read().themeMode, home: const CriticalErrorView(), ); } @@ -122,7 +99,7 @@ class _AppState extends State with WidgetsBindingObserver { title: 'twonly', theme: lightTheme, darkTheme: darkTheme, - themeMode: context.watch().themeMode, + themeMode: context.read().themeMode, ); }, ); @@ -142,7 +119,6 @@ class AppMainWidget extends StatefulWidget { class _AppMainWidgetState extends State { bool _showOnboarding = true; bool _isLoaded = false; - Object? _storageError; bool _skipBackup = kDebugMode; bool _isTwonlyLocked = true; @@ -155,29 +131,24 @@ class _AppMainWidgetState extends State { } Future initAsync() async { - try { - if (userService.isUserCreated) { - if (_isTwonlyLocked) { - // do not change in case twonly was already unlocked at some point - _isTwonlyLocked = userService.currentUser.screenLockEnabled; - } - } else { - // This means the user is in the onboarding screen, so start with the Proof of Work. - - final (proof, disabled) = await apiService.getProofOfWork(); - if (proof != null) { - Log.info('Starting with proof of work calculation.'); - _proofOfWork = ( - calculatePoW(proof.prefix, proof.difficulty.toInt()), - false, - ); - } else { - _proofOfWork = (null, disabled); - } + if (userService.isUserCreated) { + if (_isTwonlyLocked) { + // do not change in case twonly was already unlocked at some point + _isTwonlyLocked = userService.currentUser.screenLockEnabled; + } + } else { + // This means the user is in the onboarding screen, so start with the Proof of Work. + + final (proof, disabled) = await apiService.getProofOfWork(); + if (proof != null) { + Log.info('Starting with proof of work calculation.'); + _proofOfWork = ( + calculatePoW(proof.prefix, proof.difficulty.toInt()), + false, + ); + } else { + _proofOfWork = (null, disabled); } - } catch (e) { - Log.error('Storage error in AppMainWidget.initAsync: $e'); - _storageError = e; } setState(() { @@ -191,10 +162,6 @@ class _AppMainWidgetState extends State { return Center(child: Container()); } - if (_storageError != null) { - return const CriticalErrorView(); - } - late Widget child; if (userService.isUserCreated) { diff --git a/lib/main.dart b/lib/main.dart index 05ceb2fa..c10d6e62 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -126,6 +126,9 @@ void main() async { unawaited(createPushAvatars()); } + await apiService.listenToNetworkChanges(); + unawaited(apiService.connect()); + runApp( MultiProvider( providers: [ @@ -134,7 +137,7 @@ void main() async { ChangeNotifierProvider(create: (_) => ImageEditorProvider()), ChangeNotifierProvider(create: (_) => PurchasesProvider()), ], - child: const App(), + child: App(storageError: storageError), ), ); } diff --git a/lib/src/providers/connection.provider.dart b/lib/src/providers/connection.provider.dart index 29abf553..4814a3c7 100644 --- a/lib/src/providers/connection.provider.dart +++ b/lib/src/providers/connection.provider.dart @@ -4,11 +4,11 @@ import 'package:twonly/locator.dart'; class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { CustomChangeProvider() { + // The API is connected before the subscription has started so ensure that the connection state is correct + _isConnected = apiService.isConnected; _connSub = apiService.onConnectionStateUpdated.listen( updateConnectionState, ); - // The API is connected before the subscription has started so ensure that the connection state is correct - _isConnected = apiService.isConnected; } late bool _isConnected; late StreamSubscription _connSub; diff --git a/lib/src/providers/purchases.provider.dart b/lib/src/providers/purchases.provider.dart index 1a39e791..d3c58f45 100644 --- a/lib/src/providers/purchases.provider.dart +++ b/lib/src/providers/purchases.provider.dart @@ -32,8 +32,7 @@ Timer? globalForceIpaCheck; class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { PurchasesProvider() { - final purchaseUpdated = iapConnection.purchaseStream; - _subscription = purchaseUpdated.listen( + _subscription = iapConnection.purchaseStream.listen( _onPurchaseUpdate, onDone: _updateStreamOnDone, onError: _updateStreamOnError, @@ -50,6 +49,10 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { } }); + if (userService.isUserCreated) { + updatePlan(planFromString(userService.currentUser.subscriptionPlan)); + } + loadPurchases(); } @@ -73,7 +76,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { final available = await iapConnection.isAvailable(); if (!available) { storeState = StoreState.notAvailable; - Log.error('Store is not available'); + Log.warn('Store is not available'); notifyListeners(); return; } @@ -98,7 +101,6 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { isPayingUser( planFromString(userService.currentUser.subscriptionPlan), )) { - Log.info('Started IPA timer for verification.'); globalForceIpaCheck = Timer(const Duration(seconds: 5), () async { Log.info( 'Force Ipa check was not stopped. Requesting forced check...', diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index e62dce92..d7a0e7be 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -84,7 +84,7 @@ class ApiService { HashMap(); IOWebSocketChannel? _channel; // ignore: cancel_subscriptions - StreamSubscription>? connectivitySubscription; + StreamSubscription>? _connectivitySubscription; Future _connectTo(String apiUrl) async { if (appIsOutdated) return false; @@ -185,10 +185,10 @@ class ApiService { } Future listenToNetworkChanges() async { - if (connectivitySubscription != null) { + if (_connectivitySubscription != null) { return; } - connectivitySubscription = Connectivity().onConnectivityChanged.listen(( + _connectivitySubscription = Connectivity().onConnectivityChanged.listen(( result, ) async { if (!result.contains(ConnectivityResult.none)) { @@ -467,10 +467,11 @@ class ApiService { return lockAuthentication.protect(() async { if (isAuthenticated) return; if (await getSignalIdentity() == null) { + Log.error('Signal identity not found.'); return; } - if (userService.isUserCreated) return; + if (!userService.isUserCreated) return; if (await tryAuthenticateWithToken()) { return; diff --git a/lib/src/services/user.service.dart b/lib/src/services/user.service.dart index 5cb4f19f..6bdb53fa 100644 --- a/lib/src/services/user.service.dart +++ b/lib/src/services/user.service.dart @@ -20,7 +20,7 @@ class UserService { final user = await getUser(); if (user == null) return false; userService.currentUser = user; - isUserCreated = true; + userService.isUserCreated = true; return true; } diff --git a/lib/src/visual/views/camera/share_image_contact_selection.view.dart b/lib/src/visual/views/camera/share_image_contact_selection.view.dart index 9f28bc70..a0d3e9cc 100644 --- a/lib/src/visual/views/camera/share_image_contact_selection.view.dart +++ b/lib/src/visual/views/camera/share_image_contact_selection.view.dart @@ -3,7 +3,9 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -40,7 +42,7 @@ class ShareImageView extends StatefulWidget { } class _ShareImageView extends State { - List contacts = []; + List _allGroups = []; List _otherUsers = []; List _bestFriends = []; List _pinnedContacts = []; @@ -61,9 +63,9 @@ class _ShareImageView extends State { allGroups, ) async { setState(() { - contacts = allGroups; + _allGroups = allGroups; }); - await updateGroups(allGroups.where((x) => !x.archived).toList()); + await updateGroups(_allGroups.where((x) => !x.archived).toList()); }); unawaited(initAsync()); @@ -128,7 +130,7 @@ class _ShareImageView extends State { lastQuery = query; if (query.isEmpty) { await updateGroups( - contacts + _allGroups .where( (x) => !x.archived || @@ -139,7 +141,7 @@ class _ShareImageView extends State { ); return; } - final usersFiltered = contacts + final usersFiltered = _allGroups .where( (user) => user.groupName.toLowerCase().contains(query.toLowerCase()), ) @@ -168,16 +170,30 @@ class _ShareImageView extends State { ), child: Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: TextField( - onChanged: _filterUsers, - decoration: getInputDecoration( - context, - context.lang.shareImageSearchAllContacts, + if (_allGroups.isEmpty) + Expanded( + child: Center( + child: FilledButton.icon( + icon: const Icon(Icons.person_add), + onPressed: () => context.push(Routes.chatsAddNewUser), + label: Text( + context.lang.chatListViewSearchUserNameBtn, + ), + ), + ), + ), + + if (_allGroups.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: TextField( + onChanged: _filterUsers, + decoration: getInputDecoration( + context, + context.lang.shareImageSearchAllContacts, + ), ), ), - ), if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10), BestFriendsSelector( groups: _pinnedContacts, @@ -202,7 +218,7 @@ class _ShareImageView extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ HeadLineComp(context.lang.shareImageAllUsers), - if (contacts.any((x) => x.archived)) + if (_allGroups.any((x) => x.archived)) Row( children: [ Text( @@ -236,13 +252,14 @@ class _ShareImageView extends State { ), ], ), - Expanded( - child: UserList( - List.from(_otherUsers), - selectedGroupIds: widget.selectedGroupIds, - updateSelectedGroupIds: updateSelectedGroupIds, + if (_otherUsers.isNotEmpty) + Expanded( + child: UserList( + List.from(_otherUsers), + selectedGroupIds: widget.selectedGroupIds, + updateSelectedGroupIds: updateSelectedGroupIds, + ), ), - ), ], ), ), diff --git a/lib/src/visual/views/chats/chat_list.view.dart b/lib/src/visual/views/chats/chat_list.view.dart index 58200324..40e8a866 100644 --- a/lib/src/visual/views/chats/chat_list.view.dart +++ b/lib/src/visual/views/chats/chat_list.view.dart @@ -218,7 +218,7 @@ class _ChatListViewState extends State { ? Center( child: Padding( padding: const EdgeInsets.all(10), - child: OutlinedButton.icon( + child: FilledButton.icon( icon: const Icon(Icons.person_add), onPressed: () => context.push(Routes.chatsAddNewUser), label: Text(