From 1d2ab18b9fa14cb398ee9822bdb5f4df9e58e156 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:51:25 +0200 Subject: [PATCH] bug fixes --- integration_test/two_step_rollout_test.dart | 4 +- integration_test/views_test.dart | 2 +- lib/l10n/app_cs.arb | 9 ++++ lib/l10n/app_de.arb | 9 ++++ lib/l10n/app_en.arb | 9 ++++ lib/l10n/app_es.arb | 9 ++++ lib/l10n/app_fr.arb | 9 ++++ lib/l10n/app_nl.arb | 9 ++++ lib/l10n/app_pl.arb | 9 ++++ lib/model/riverpod_states/token_state.dart | 8 +-- lib/model/token_folder.dart | 9 +++- .../customization/theme_customization.dart | 12 ++--- lib/utils/home_widget_utils.dart | 7 +-- .../token_folder_notifier.dart | 1 + .../generated_providers/token_notifier.dart | 32 +++++++---- .../container_widgets/container_widget.dart | 53 +++++++++++-------- .../pages/import_plain_tokens_page.dart | 46 +++++++--------- .../settings_view_widgets/settings_group.dart | 2 +- lib/widgets/app_wrapper.dart | 4 +- .../add_container_progress_dialog.dart | 29 ++++++---- 20 files changed, 177 insertions(+), 95 deletions(-) diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index b3a75711d..0fd8ec4dd 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -71,7 +71,7 @@ Future _addTwoStepHotpTokenTest(WidgetTester tester) async { const qrCode = 'otpauth://hotp/OATH0001DBD0?secret=AALIBQJMOGEE7SAVEZ5D3K2ADO7MVFQD&counter=1&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; final notifier = globalRef!.read(tokenProvider.notifier); - await scanQrCode([notifier], qrCode); + await scanQrCode(resultHandlerList: [notifier], qrCode: qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); @@ -95,7 +95,7 @@ Future _addTwoStepTotpTokenTest(WidgetTester tester) async { const qrCode = 'otpauth://totp/TOTP00009D5F?secret=NZ4OPONKAAGDFN2QHV26ZWYVTLFER4C6&period=30&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; final notifier = globalRef!.read(tokenProvider.notifier); - await scanQrCode([notifier], qrCode); + await scanQrCode(resultHandlerList: [notifier], qrCode: qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index ca17dc88d..50b2a152c 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -170,7 +170,7 @@ Future _settingsViewTest(WidgetTester tester) async { const qrCode = 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_container=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'; final notifier = globalRef!.read(tokenProvider.notifier); - await scanQrCode([notifier], qrCode); + await scanQrCode(resultHandlerList: [notifier], qrCode: qrCode); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().pushToken), 1, const Duration(minutes: 5)); expect(find.text(AppLocalizationsEn().pushToken), findsOneWidget); diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 782f6616c..e8c2d964d 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "Lze exportovat?", "isPiTokenQuestion": "Je to token privacyIDEA?", + "issuer": "Issuer: {name}", "language": "Jazyk", "legacySigningErrorMessage": "Token byl vytvořen v zastaralé verzi aplikace, což může vést k problémům při jeho používání.\nPokud problém přetrvává, doporučujeme vytvořit nový push token!", "legacySigningErrorTitle": "Při použití staršího tokenu došlo k chybě: {tokenLabel}", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d30959754..03c3beb45 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "Ist exportierbar?", "isPiTokenQuestion": "Ist ein privacyIDEA-Token?", + "issuer": "Issuer: {name}", "language": "Sprache", "legacySigningErrorMessage": "Der Token wurde in einer veralteten Version der App erstellt, was zu Problemen bei der Verwendung führen kann. Es wird empfohlen, einen neuen Push-Token zu erstellen, wenn das Problem weiterhin besteht!", "legacySigningErrorTitle": "Bei der Verwendung des veralteten Tokens ist ein Fehler aufgetreten: {tokenLabel}", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2e0b0a2e4..9b306d53f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "The {type} \"{value}\" is not valid for \"{parameter}\" in \"{map}\"", "isExpotableQuestion": "Is exportable?", "isPiTokenQuestion": "It's a privacyIDEA token?", + "issuer": "Issuer: {name}", "language": "Language", "legacySigningErrorMessage": "The token was enrolled in a old version of this app, which may cause trouble using it.\nIt is suggested to enroll a new push token if the problem persist!", "legacySigningErrorTitle": "An error occured while using the legacy token: {tokenLabel}", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 507185b8f..befe619d0 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "¿Es exportable?", "isPiTokenQuestion": "¿Es un token de privacyIDEA?", + "issuer": "Issuer: {name}", "language": "Language", "legacySigningErrorMessage": "El token se creó en una versión obsoleta de la aplicación, lo que puede provocar problemas al utilizarlo.\nSe recomienda crear un nuevo token push si el problema persiste.", "legacySigningErrorTitle": "Se ha producido un error al utilizar el token obsoleto: {tokenLabel}", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1296546b1..a04ee088a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "Est exportable ?", "isPiTokenQuestion": "C'est un jeton privacyIDEA ?", + "issuer": "Issuer: {name}", "language": "Langue", "legacySigningErrorMessage": "Le token a été créé dans une version obsolète de l'application, ce qui peut entraîner des problèmes d'utilisation.\nIl est recommandé de créer un nouveau token push si le problème persiste !", "legacySigningErrorTitle": "Une erreur s'est produite lors de l'utilisation du jeton obsolète : {tokenLabel}", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 11b0bd091..893671b55 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "Is exporteerbaar?", "isPiTokenQuestion": "Is het een privacyIDEA token?", + "issuer": "Issuer: {name}", "language": "Taal", "legacySigningErrorMessage": "Het token is aangemaakt in een verouderde versie van de app, wat kan leiden tot problemen bij het gebruik ervan.\nHet wordt aanbevolen om een nieuw push token aan te maken als het probleem zich blijft voordoen!", "legacySigningErrorTitle": "Er is een fout opgetreden bij het gebruik van het verouderde token: {tokenLabel}", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 1e5c8efcf..e71d9906c 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -266,6 +266,14 @@ "@isPiTokenQuestion": { "description": "Label for the question if the token is a privacyIDEA token." }, + "@issuer": { + "description": "Label for the issuer of the token, container... etc.", + "placeholders": { + "name": { + "example": "privacyIDEA" + } + } + }, "@language": { "description": "Title of language setting group." }, @@ -771,6 +779,7 @@ "invalidValueIn": "untranslated", "isExpotableQuestion": "Czy można eksportować?", "isPiTokenQuestion": "To jest token privacyIDEA?", + "issuer": "Issuer: {name}", "language": "Język", "legacySigningErrorMessage": "Token został utworzony w nieaktualnej wersji aplikacji, co może prowadzić do problemów podczas korzystania z niego.\nZaleca się utworzenie nowego tokena push, jeśli problem nadal występuje!", "legacySigningErrorTitle": "Wystąpił błąd podczas korzystania z przestarzałego tokena: {tokenLabel}", diff --git a/lib/model/riverpod_states/token_state.dart b/lib/model/riverpod_states/token_state.dart index 41a03c642..ee370e037 100644 --- a/lib/model/riverpod_states/token_state.dart +++ b/lib/model/riverpod_states/token_state.dart @@ -62,10 +62,10 @@ class TokenState { PushToken? getTokenBySerial(String serial) => pushTokens.firstWhereOrNull((element) => element.serial == serial); - /// Maps the given tokens to the tokens that are already in the state - /// It ignores the id that is usually used to identify the token - /// Instead it uses the non-changeable values of the token to identify it - /// Like the secret and hash algorithm for OTP tokens, or the serial and public server key for push tokens + /// Maps the given tokens to the tokens that are already in the state. + /// It ignores the id that is usually used to identify the token. + /// Instead it uses the non-changeable values of the token to identify it. + /// Like the secret, hash algorithm, serial and public server key for push tokens. Map getSameTokens(List tokens) { final sameTokensMap = {}; final stateTokens = this.tokens; diff --git a/lib/model/token_folder.dart b/lib/model/token_folder.dart index 356843941..3e2d65c28 100644 --- a/lib/model/token_folder.dart +++ b/lib/model/token_folder.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* * privacyIDEA Authenticator * @@ -76,8 +78,11 @@ class TokenFolder with SortableMixin { factory TokenFolder.fromJson(Map json) { var tokenFolder = _$TokenFolderFromJson(json); - if (tokenFolder.isLocked) tokenFolder = tokenFolder.copyWith(isExpanded: false); - return tokenFolder; + if (tokenFolder.isLocked) { + return tokenFolder.copyWith(isExpanded: false); + } else { + return tokenFolder; + } } Map toJson() => _$TokenFolderToJson(this); } diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart index 2322eee87..d1c86300e 100644 --- a/lib/utils/customization/theme_customization.dart +++ b/lib/utils/customization/theme_customization.dart @@ -87,9 +87,9 @@ class ThemeCustomization { backgroundColor = backgroundColor ?? const Color(0xffEFEFEF), foregroundColor = foregroundColor ?? const Color(0xff282828), shadowColor = shadowColor ?? const Color(0x4C303030), - deleteColor = deleteColor ?? const Color(0xffE04D2D), - renameColor = renameColor ?? const Color(0xff6A8FE5), - lockColor = lockColor ?? const Color(0xffFFD633), + deleteColor = deleteColor ?? const Color(0xffe85e40), + renameColor = renameColor ?? const Color(0xff7f9bdd), + lockColor = lockColor ?? const Color(0xffffd633), tileIconColor = tileIconColor ?? const Color(0xff757575), navigationBarColor = navigationBarColor ?? const Color(0xFFFFFFFF), // From here on the colors have a default value based on another given color so they can be null @@ -130,9 +130,9 @@ class ThemeCustomization { backgroundColor = backgroundColor ?? const Color(0xFF303030), foregroundColor = foregroundColor ?? const Color(0xffF5F5F5), shadowColor = shadowColor ?? const Color(0x4CEFEFEF), - deleteColor = deleteColor ?? const Color(0xffCD3C14), - renameColor = renameColor ?? const Color(0xff527EDB), - lockColor = lockColor ?? const Color(0xffFFCC00), + deleteColor = deleteColor ?? const Color(0xffb93f1d), + renameColor = renameColor ?? const Color(0xff4a72c6), + lockColor = lockColor ?? const Color(0xffe4ba11), tileIconColor = tileIconColor ?? const Color(0xffF5F5F5), navigationBarColor = navigationBarColor ?? const Color(0xFF282828), // From here on the colors have a default value based on another given color so they can be null diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 48a2deb85..101971a70 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -50,7 +50,6 @@ import '../widgets/home_widgets/home_widget_otp.dart'; import '../widgets/home_widgets/home_widget_unlinked.dart'; import 'globals.dart'; import 'logger.dart'; -import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/home_widget_provider.dart'; const appGroupId = 'group.authenticator_home_widget_group'; @@ -79,7 +78,7 @@ class HomeWidgetUtils { static const _widgetActionSize = Size(24 * 2, 24 * 2); /// Default duration for showing the OTP - static const _showDuration = Duration(seconds: 15); + static const _showDuration = Duration(seconds: 10); factory HomeWidgetUtils({TokenRepository? tokenRepository, TokenFolderRepository? tokenFolderRepository}) { if (kIsWeb || Platform.isIOS) return UnsupportedHomeWidgetUtils(); // Not supported on iOS @@ -101,10 +100,6 @@ class HomeWidgetUtils { static TokenRepository? _tokenRepository; static TokenFolderRepository? _folderRepository; static final Mutex _repoMutex = Mutex(); - static Future> get _otpTokens async { - final tokens = globalRef?.read(tokenProvider).tokens; - return tokens?.whereType().toList() ?? (await _loadTokensFromRepo()).whereType().toList(); - } static Future _getTokenOfTokenId(String tokenId) async { await _repoMutex.acquire(); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart index f74673385..8c022f267 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart @@ -180,6 +180,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { _stateMutex.release(); return oldState; } + state = newState; _stateMutex.release(); return newState; } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 42d5b49e3..954047eed 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -159,6 +159,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a list of tokens and returns the tokens that could not be added or replaced. Future> _addOrReplaceTokens(List tokens) async { + tokens = _removeDuplicates(tokens); if (tokens.isEmpty) return []; Logger.debug('Adding ${tokens.length} tokens.', verbose: true); await _repoMutex.acquire(); @@ -377,10 +378,10 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { Future> addOrReplaceTokens(List tokens) => _addOrReplaceTokens(tokens); /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. - Future updateToken(T token, T Function(T) updater) async => _updateToken(token, updater); + Future updateToken(T token, T Function(T) updater) => _updateToken(token, updater); /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. - Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); + Future> updateTokens(List tokens, T Function(T) updater) => _updateTokens(tokens, updater); /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); @@ -437,20 +438,20 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } /// Minimizing the app needs to cancel all timers and save the state to the repository. - Future saveStateOnMinimizeApp() async { + Future onMinimizeApp() { + Logger.info('TokenNotifier: Preparing to minimize app.'); _cancelTimers(); - await hideLockedTokens(); - return _saveStateToRepo(state); + return hideLockedTokens(); } - Future> hideLockedTokens() async { - final hideLockedTokens = []; + Future hideLockedTokens() async { + final lockedTokens = []; for (var token in state.tokens) { if (token.isLocked && !token.isHidden) { - hideLockedTokens.add(token); + lockedTokens.add(token); } } - return await updateTokens(hideLockedTokens, (p0) => p0.copyWith(isHidden: true)); + return (await updateTokens(lockedTokens, (p0) => p0.copyWith(isHidden: true))).length == lockedTokens.length; } /// Removes a token from the state and the repository. @@ -800,6 +801,16 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { ///////////////////////////// Helper Methods /////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// */ + List _removeDuplicates(List tokens) { + final uniqueTokens = []; + for (var token in tokens) { + if (!uniqueTokens.any((uniqureToken) => uniqureToken.isSameTokenAs(token))) { + uniqueTokens.add(token); + } + } + return uniqueTokens; + } + Future _showImportTokensPage( List> tokenResults, TokenOriginSourceType tokenOriginSourceType, @@ -814,9 +825,8 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { ), ), ); - if (tokensToKeep == null) return; - final tokensWithSourceType = _addSourceType(tokenResults.getData(), tokenOriginSourceType); + final tokensWithSourceType = _addSourceType(tokensToKeep, tokenOriginSourceType); await addNewTokens(tokensWithSourceType); } diff --git a/lib/views/container_view/container_widgets/container_widget.dart b/lib/views/container_view/container_widgets/container_widget.dart index ad16afe20..a20be9e9a 100644 --- a/lib/views/container_view/container_widgets/container_widget.dart +++ b/lib/views/container_view/container_widgets/container_widget.dart @@ -19,6 +19,8 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/rollout_state_extension.dart'; import '../../../model/token_container.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; @@ -48,33 +50,42 @@ class ContainerWidget extends ConsumerWidget { groupTag: groupTag, identifier: container.serial, actions: [ - if (!isPreview) DeleteContainerAction(container: container, key: Key('${container.serial}-DeleteContainerAction')), - if (!isPreview) EditContainerAction(container: container, key: Key('${container.serial}-EditContainerAction')), + DeleteContainerAction(container: container, key: Key('${container.serial}-DeleteContainerAction')), + EditContainerAction(container: container, key: Key('${container.serial}-EditContainerAction')), ], stack: stack, child: TokenWidgetTile( - title: Text(container.serial), + title: Text( + container.serial, + style: Theme.of(context).textTheme.titleMedium, + ), subtitles: [ - 'issuer: ${container.issuer}', - 'finalizationState: ${container.finalizationState.name}', + AppLocalizations.of(context)!.issuer(container.issuer), + '${container.finalizationState.rolloutMsg(AppLocalizations.of(context)!)}', ], - trailing: isPreview - ? null - : container is TokenContainerFinalized - ? IconButton( - icon: const Icon(Icons.sync), - onPressed: () { - final tokenState = ref.read(tokenProvider); - ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); - }, - ) - : IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - ref.read(tokenContainerProvider.notifier).deleteContainer(container); - }, - ), + trailing: _getTrailing(context, ref), ), ), ); + + Widget _getTrailing(BuildContext context, WidgetRef ref) { + if (container is TokenContainerFinalized) { + return IconButton( + icon: const Icon(Icons.sync), + onPressed: () { + final tokenState = ref.read(tokenProvider); + ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); + }, + ); + } + if (container.finalizationState.isFailed) { + return IconButton( + icon: const Icon(Icons.sync_problem), + onPressed: () { + ref.read(tokenContainerProvider.notifier).finalize(container); + }, + ); + } + return SizedBox(); + } } diff --git a/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart b/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart index e3efcc040..2b23c9845 100644 --- a/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart +++ b/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart @@ -73,7 +73,6 @@ class _ImportFileNoPwState extends ConsumerState { List? tokensToKeep; bool isMaxScrollOffset = true; - List importTokenEntrys = []; final List conflictedImports = []; final List newImports = []; final List appDuplicates = []; @@ -98,38 +97,21 @@ class _ImportFileNoPwState extends ConsumerState { } conflictedImports.add(importTokenEntry); } + _setTokensToKeep([...newImports, ...conflictedImports]); return importTokenEntrys; } - void _renewLists(List importTokenEntrys) { - newImports.clear(); - appDuplicates.clear(); - conflictedImports.clear(); - for (final importTokenEntry in importTokenEntrys) { - if (importTokenEntry.oldToken == null) { - newImports.add(importTokenEntry); - continue; - } - if (importTokenEntry.newToken.sameValuesAs(importTokenEntry.oldToken!)) { - appDuplicates.add(importTokenEntry); - continue; - } - conflictedImports.add(importTokenEntry); - } - } - @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final map = ref.read(tokenProvider).getSameTokens(widget.importedTokens); - importTokenEntrys = []; + final importTokenEntrys = []; setState(() { map.forEach((key, value) { importTokenEntrys.add(TokenImportEntry(newToken: key, oldToken: value)); }); - importTokenEntrys = _initBuildLists(importTokenEntrys); - _setTokensToKeep(importTokenEntrys); + _initBuildLists(importTokenEntrys); }); }); scrollController.addListener(_updateIsMaxScrollExtent); @@ -200,7 +182,7 @@ class _ImportFileNoPwState extends ConsumerState { titlePadding: const EdgeInsets.symmetric(horizontal: 40), leadingDivider: widget.failedImports.isNotEmpty, importEntries: conflictedImports, - onTap: _updateImportTokenEntry, + onTap: _updateConflicted, ), if (newImports.isNotEmpty) NoConflictImportTokensList( @@ -208,7 +190,7 @@ class _ImportFileNoPwState extends ConsumerState { titlePadding: const EdgeInsets.symmetric(horizontal: 40), leadingDivider: conflictedImports.isNotEmpty, importEntries: newImports, - onTap: _updateImportTokenEntry, + onTap: _updateNewImports, ), if (appDuplicates.isNotEmpty) NoConflictImportTokensList( @@ -269,15 +251,23 @@ class _ImportFileNoPwState extends ConsumerState { ); } - void _updateImportTokenEntry(TokenImportEntry oldEntry, TokenImportEntry newEntry) { + void _updateConflicted(TokenImportEntry oldEntry, TokenImportEntry newEntry) { + setState(() { + final i = conflictedImports.indexOf(oldEntry); + conflictedImports[i] = newEntry; + _setTokensToKeep([...newImports, ...conflictedImports]); + }); + } + + void _updateNewImports(TokenImportEntry oldEntry, TokenImportEntry newEntry) { setState(() { - importTokenEntrys[importTokenEntrys.indexOf(oldEntry)] = newEntry; - _renewLists(importTokenEntrys); - _setTokensToKeep(importTokenEntrys); + final i = newImports.indexOf(oldEntry); + newImports[i] = newEntry; + _setTokensToKeep([...newImports, ...conflictedImports]); }); } - void _setTokensToKeep(List tokens) { + void _setTokensToKeep(List importTokenEntrys) { tokensToKeep = []; for (final importTokenEntry in importTokenEntrys) { if (importTokenEntry.oldToken != null) { diff --git a/lib/views/settings_view/settings_view_widgets/settings_group.dart b/lib/views/settings_view/settings_view_widgets/settings_group.dart index 6de176704..c186db086 100644 --- a/lib/views/settings_view/settings_view_widgets/settings_group.dart +++ b/lib/views/settings_view/settings_view_widgets/settings_group.dart @@ -90,7 +90,7 @@ class SettingsGroup extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.fromLTRB(4, 0, 4, 4), child: Column(children: children), ), ], diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index c840ee695..5317070b5 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -54,10 +54,10 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { }, // onInactive: () => log('App inactive'), onHide: () async { - if (await ref.read(tokenProvider.notifier).saveStateOnMinimizeApp() == false) { + if (await ref.read(tokenProvider.notifier).onMinimizeApp() == false) { Logger.error('Failed to save tokens on Hide'); } - if (await ref.read(tokenFolderProvider.notifier).collapseLockedFolders() == false) { + if ((await ref.read(tokenFolderProvider.notifier).collapseLockedFolders()).folders.any((element) => element.isExpanded)) { Logger.error('Failed to collapse locked folders on Hide'); } await FlutterLocalNotificationsPlugin().cancelAll(); diff --git a/lib/widgets/dialog_widgets/add_container_progress_dialog.dart b/lib/widgets/dialog_widgets/add_container_progress_dialog.dart index f5aa9ea89..fd3dd9b6d 100644 --- a/lib/widgets/dialog_widgets/add_container_progress_dialog.dart +++ b/lib/widgets/dialog_widgets/add_container_progress_dialog.dart @@ -40,15 +40,19 @@ class AddContainerProgressDialog extends StatelessWidget { return DefaultDialog( hasCloseButton: true, title: Text(serials.length == 1 ? 'Adding container' : 'Adding containers'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (var serial in serials) ...[ - AddContainerProgressDialogTile(serial), - if (serial != serials.last) const Divider(), - if (serial != serials.last) const Divider(), - ] - ], + content: SizedBox( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + for (var serial in serials) ...[ + AddContainerProgressDialogTile(serial), + if (serial != serials.last) const Divider(), + if (serial != serials.last) const Divider(), + ] + ], + ), ), actions: [ TextButton( @@ -74,8 +78,8 @@ class AddContainerProgressDialogTile extends ConsumerWidget { ContainerWidget(container: container, isPreview: true), ]; if (container.finalizationState.isFailed) return Column(children: children); - children.add(Divider()); if (container is TokenContainerFinalized && containerTokens.isNotEmpty) { + children.add(Divider()); for (final token in containerTokens) { children.add( SizedBox( @@ -85,7 +89,10 @@ class AddContainerProgressDialogTile extends ConsumerWidget { ); } } else { - children.add(const CircularProgressIndicator()); + children.add(Padding( + padding: const EdgeInsets.only(top: 16), + child: const CircularProgressIndicator(), + )); } return Column(children: children); }