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/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<App> createState() => _AppState();
}
class _AppState extends State<App> 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<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
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<App> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: context.watch<SettingsChangeProvider>(),
listenable: context.read<SettingsChangeProvider>(),
builder: (context, child) {
const localizationsDelegates = [
AppLocalizations.delegate,
@ -99,7 +76,7 @@ class _AppState extends State<App> 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<App> with WidgetsBindingObserver {
title: 'twonly',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: context.watch<SettingsChangeProvider>().themeMode,
themeMode: context.read<SettingsChangeProvider>().themeMode,
home: const CriticalErrorView(),
);
}
@ -122,7 +99,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
title: 'twonly',
theme: lightTheme,
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> {
bool _showOnboarding = true;
bool _isLoaded = false;
Object? _storageError;
bool _skipBackup = kDebugMode;
bool _isTwonlyLocked = true;
@ -155,29 +131,24 @@ class _AppMainWidgetState extends State<AppMainWidget> {
}
Future<void> 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<AppMainWidget> {
return Center(child: Container());
}
if (_storageError != null) {
return const CriticalErrorView();
}
late Widget child;
if (userService.isUserCreated) {

View file

@ -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),
),
);
}

View file

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

View file

@ -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...',

View file

@ -84,7 +84,7 @@ class ApiService {
HashMap();
IOWebSocketChannel? _channel;
// ignore: cancel_subscriptions
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription;
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
Future<bool> _connectTo(String apiUrl) async {
if (appIsOutdated) return false;
@ -185,10 +185,10 @@ class ApiService {
}
Future<void> 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;

View file

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

View file

@ -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<ShareImageView> {
List<Group> contacts = [];
List<Group> _allGroups = [];
List<Group> _otherUsers = [];
List<Group> _bestFriends = [];
List<Group> _pinnedContacts = [];
@ -61,9 +63,9 @@ class _ShareImageView extends State<ShareImageView> {
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<ShareImageView> {
lastQuery = query;
if (query.isEmpty) {
await updateGroups(
contacts
_allGroups
.where(
(x) =>
!x.archived ||
@ -139,7 +141,7 @@ class _ShareImageView extends State<ShareImageView> {
);
return;
}
final usersFiltered = contacts
final usersFiltered = _allGroups
.where(
(user) => user.groupName.toLowerCase().contains(query.toLowerCase()),
)
@ -168,16 +170,30 @@ class _ShareImageView extends State<ShareImageView> {
),
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<ShareImageView> {
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<ShareImageView> {
),
],
),
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,
),
),
),
],
),
),

View file

@ -218,7 +218,7 @@ class _ChatListViewState extends State<ChatListView> {
? 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(