bug fixes

This commit is contained in:
otsmr 2026-04-26 02:10:16 +02:00
parent a7d64a2307
commit dcca2cbec0
8 changed files with 82 additions and 92 deletions

View file

@ -7,10 +7,8 @@ import 'package:provider/provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/localization/generated/app_localizations.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/routing.provider.dart';
import 'package:twonly/src/providers/settings.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/log.dart';
import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/pow.dart';
import 'package:twonly/src/visual/components/app_outdated.comp.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'; import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
class App extends StatefulWidget { class App extends StatefulWidget {
const App({super.key}); const App({required this.storageError, super.key});
final bool storageError;
@override @override
State<App> createState() => _AppState(); State<App> createState() => _AppState();
} }
class _AppState extends State<App> with WidgetsBindingObserver { class _AppState extends State<App> with WidgetsBindingObserver {
bool wasPaused = false; bool _wasPaused = false;
Object? _storageError;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
AppState.isAppInBackground = false; AppState.isAppInBackground = false;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
unawaited(initAsync());
}
Future<void> initAsync() async {
try {
if (userService.isUserCreated && mounted) {
context.read<PurchasesProvider>().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 @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
if (wasPaused) { if (_wasPaused) {
AppState.isAppInBackground = false; AppState.isAppInBackground = false;
twonlyDB.markUpdated(); twonlyDB.markUpdated();
unawaited(apiService.connect()); unawaited(apiService.connect());
} }
} else if (state == AppLifecycleState.paused) { } else if (state == AppLifecycleState.paused) {
wasPaused = true; _wasPaused = true;
AppState.isAppInBackground = true; AppState.isAppInBackground = true;
} }
} }
@ -85,7 +62,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListenableBuilder( return ListenableBuilder(
listenable: context.watch<SettingsChangeProvider>(), listenable: context.read<SettingsChangeProvider>(),
builder: (context, child) { builder: (context, child) {
const localizationsDelegates = [ const localizationsDelegates = [
AppLocalizations.delegate, AppLocalizations.delegate,
@ -99,7 +76,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
Locale('de', ''), Locale('de', ''),
]; ];
if (_storageError != null) { if (widget.storageError) {
return MaterialApp( return MaterialApp(
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey, scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
localizationsDelegates: localizationsDelegates, localizationsDelegates: localizationsDelegates,
@ -108,7 +85,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
title: 'twonly', title: 'twonly',
theme: lightTheme, theme: lightTheme,
darkTheme: darkTheme, darkTheme: darkTheme,
themeMode: context.watch<SettingsChangeProvider>().themeMode, themeMode: context.read<SettingsChangeProvider>().themeMode,
home: const CriticalErrorView(), home: const CriticalErrorView(),
); );
} }
@ -122,7 +99,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
title: 'twonly', title: 'twonly',
theme: lightTheme, theme: lightTheme,
darkTheme: darkTheme, darkTheme: darkTheme,
themeMode: context.watch<SettingsChangeProvider>().themeMode, themeMode: context.read<SettingsChangeProvider>().themeMode,
); );
}, },
); );
@ -142,7 +119,6 @@ class AppMainWidget extends StatefulWidget {
class _AppMainWidgetState extends State<AppMainWidget> { class _AppMainWidgetState extends State<AppMainWidget> {
bool _showOnboarding = true; bool _showOnboarding = true;
bool _isLoaded = false; bool _isLoaded = false;
Object? _storageError;
bool _skipBackup = kDebugMode; bool _skipBackup = kDebugMode;
bool _isTwonlyLocked = true; bool _isTwonlyLocked = true;
@ -155,7 +131,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
try {
if (userService.isUserCreated) { if (userService.isUserCreated) {
if (_isTwonlyLocked) { if (_isTwonlyLocked) {
// do not change in case twonly was already unlocked at some point // do not change in case twonly was already unlocked at some point
@ -175,10 +150,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
_proofOfWork = (null, disabled); _proofOfWork = (null, disabled);
} }
} }
} catch (e) {
Log.error('Storage error in AppMainWidget.initAsync: $e');
_storageError = e;
}
setState(() { setState(() {
_isLoaded = true; _isLoaded = true;
@ -191,10 +162,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
return Center(child: Container()); return Center(child: Container());
} }
if (_storageError != null) {
return const CriticalErrorView();
}
late Widget child; late Widget child;
if (userService.isUserCreated) { if (userService.isUserCreated) {

View file

@ -126,6 +126,9 @@ void main() async {
unawaited(createPushAvatars()); unawaited(createPushAvatars());
} }
await apiService.listenToNetworkChanges();
unawaited(apiService.connect());
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
@ -134,7 +137,7 @@ void main() async {
ChangeNotifierProvider(create: (_) => ImageEditorProvider()), ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
ChangeNotifierProvider(create: (_) => PurchasesProvider()), ChangeNotifierProvider(create: (_) => PurchasesProvider()),
], ],
child: const App(), child: App(storageError: storageError),
), ),
); );
} }

View file

@ -4,11 +4,11 @@ import 'package:twonly/locator.dart';
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
CustomChangeProvider() { 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( _connSub = apiService.onConnectionStateUpdated.listen(
updateConnectionState, 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 bool _isConnected;
late StreamSubscription<bool> _connSub; late StreamSubscription<bool> _connSub;

View file

@ -32,8 +32,7 @@ Timer? globalForceIpaCheck;
class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
PurchasesProvider() { PurchasesProvider() {
final purchaseUpdated = iapConnection.purchaseStream; _subscription = iapConnection.purchaseStream.listen(
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate, _onPurchaseUpdate,
onDone: _updateStreamOnDone, onDone: _updateStreamOnDone,
onError: _updateStreamOnError, onError: _updateStreamOnError,
@ -50,6 +49,10 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
}); });
if (userService.isUserCreated) {
updatePlan(planFromString(userService.currentUser.subscriptionPlan));
}
loadPurchases(); loadPurchases();
} }
@ -73,7 +76,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
final available = await iapConnection.isAvailable(); final available = await iapConnection.isAvailable();
if (!available) { if (!available) {
storeState = StoreState.notAvailable; storeState = StoreState.notAvailable;
Log.error('Store is not available'); Log.warn('Store is not available');
notifyListeners(); notifyListeners();
return; return;
} }
@ -98,7 +101,6 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
isPayingUser( isPayingUser(
planFromString(userService.currentUser.subscriptionPlan), planFromString(userService.currentUser.subscriptionPlan),
)) { )) {
Log.info('Started IPA timer for verification.');
globalForceIpaCheck = Timer(const Duration(seconds: 5), () async { globalForceIpaCheck = Timer(const Duration(seconds: 5), () async {
Log.info( Log.info(
'Force Ipa check was not stopped. Requesting forced check...', 'Force Ipa check was not stopped. Requesting forced check...',

View file

@ -84,7 +84,7 @@ class ApiService {
HashMap(); HashMap();
IOWebSocketChannel? _channel; IOWebSocketChannel? _channel;
// ignore: cancel_subscriptions // ignore: cancel_subscriptions
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription; StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
Future<bool> _connectTo(String apiUrl) async { Future<bool> _connectTo(String apiUrl) async {
if (appIsOutdated) return false; if (appIsOutdated) return false;
@ -185,10 +185,10 @@ class ApiService {
} }
Future<void> listenToNetworkChanges() async { Future<void> listenToNetworkChanges() async {
if (connectivitySubscription != null) { if (_connectivitySubscription != null) {
return; return;
} }
connectivitySubscription = Connectivity().onConnectivityChanged.listen(( _connectivitySubscription = Connectivity().onConnectivityChanged.listen((
result, result,
) async { ) async {
if (!result.contains(ConnectivityResult.none)) { if (!result.contains(ConnectivityResult.none)) {
@ -467,10 +467,11 @@ class ApiService {
return lockAuthentication.protect(() async { return lockAuthentication.protect(() async {
if (isAuthenticated) return; if (isAuthenticated) return;
if (await getSignalIdentity() == null) { if (await getSignalIdentity() == null) {
Log.error('Signal identity not found.');
return; return;
} }
if (userService.isUserCreated) return; if (!userService.isUserCreated) return;
if (await tryAuthenticateWithToken()) { if (await tryAuthenticateWithToken()) {
return; return;

View file

@ -20,7 +20,7 @@ class UserService {
final user = await getUser(); final user = await getUser();
if (user == null) return false; if (user == null) return false;
userService.currentUser = user; userService.currentUser = user;
isUserCreated = true; userService.isUserCreated = true;
return true; return true;
} }

View file

@ -3,7 +3,9 @@ import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.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/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
@ -40,7 +42,7 @@ class ShareImageView extends StatefulWidget {
} }
class _ShareImageView extends State<ShareImageView> { class _ShareImageView extends State<ShareImageView> {
List<Group> contacts = []; List<Group> _allGroups = [];
List<Group> _otherUsers = []; List<Group> _otherUsers = [];
List<Group> _bestFriends = []; List<Group> _bestFriends = [];
List<Group> _pinnedContacts = []; List<Group> _pinnedContacts = [];
@ -61,9 +63,9 @@ class _ShareImageView extends State<ShareImageView> {
allGroups, allGroups,
) async { ) async {
setState(() { setState(() {
contacts = allGroups; _allGroups = allGroups;
}); });
await updateGroups(allGroups.where((x) => !x.archived).toList()); await updateGroups(_allGroups.where((x) => !x.archived).toList());
}); });
unawaited(initAsync()); unawaited(initAsync());
@ -128,7 +130,7 @@ class _ShareImageView extends State<ShareImageView> {
lastQuery = query; lastQuery = query;
if (query.isEmpty) { if (query.isEmpty) {
await updateGroups( await updateGroups(
contacts _allGroups
.where( .where(
(x) => (x) =>
!x.archived || !x.archived ||
@ -139,7 +141,7 @@ class _ShareImageView extends State<ShareImageView> {
); );
return; return;
} }
final usersFiltered = contacts final usersFiltered = _allGroups
.where( .where(
(user) => user.groupName.toLowerCase().contains(query.toLowerCase()), (user) => user.groupName.toLowerCase().contains(query.toLowerCase()),
) )
@ -168,6 +170,20 @@ class _ShareImageView extends State<ShareImageView> {
), ),
child: Column( child: Column(
children: [ children: [
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(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField( child: TextField(
@ -202,7 +218,7 @@ class _ShareImageView extends State<ShareImageView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
HeadLineComp(context.lang.shareImageAllUsers), HeadLineComp(context.lang.shareImageAllUsers),
if (contacts.any((x) => x.archived)) if (_allGroups.any((x) => x.archived))
Row( Row(
children: [ children: [
Text( Text(
@ -236,6 +252,7 @@ class _ShareImageView extends State<ShareImageView> {
), ),
], ],
), ),
if (_otherUsers.isNotEmpty)
Expanded( Expanded(
child: UserList( child: UserList(
List.from(_otherUsers), List.from(_otherUsers),

View file

@ -218,7 +218,7 @@ class _ChatListViewState extends State<ChatListView> {
? Center( ? Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: OutlinedButton.icon( child: FilledButton.icon(
icon: const Icon(Icons.person_add), icon: const Icon(Icons.person_add),
onPressed: () => context.push(Routes.chatsAddNewUser), onPressed: () => context.push(Routes.chatsAddNewUser),
label: Text( label: Text(