diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d986605a..e86c4d1b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -123,9 +123,15 @@ "@container": { "description": "Title for the container view." }, + "@containerDetails": { + "description": "Title of the container details dialog." + }, "@containerSerial": { "description": "description" }, + "@containerSyncUrl": { + "description": "Title of the container sync url field." + }, "@continueButton": { "description": "description" }, @@ -194,6 +200,9 @@ "@deleteLockedToken": { "description": "description" }, + "@details": { + "description": "Title of the details action Button." + }, "@deviceCredentialsRequiredTitle": { "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, @@ -307,6 +316,9 @@ } } }, + "@exampleUrl": { + "description": "Shows the user an example of a valid URL." + }, "@expandLockedFolder": { "description": "description" }, @@ -381,6 +393,9 @@ "@fileSavedToDownloadsFolder": { "description": "description" }, + "@finalizationState": { + "description": "Title of the finalization state field." + }, "@finalizeContainerFailed": { "description": "description" }, @@ -417,6 +432,9 @@ "@hidePushTokensDescription": { "description": "description" }, + "@imageUrl": { + "description": "Title of the image url field." + }, "@importConflictToken": { "description": "description", "placeholders": { @@ -600,6 +618,9 @@ "@isExpotableQuestion": { "description": "Label for the question if the token is exportable." }, + "@issuer": { + "description": "Title of the issuer field." + }, "@issuerLabel": { "description": "Label for the issuer of the token, container... etc.", "placeholders": { @@ -1039,6 +1060,21 @@ "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, + "@syncState": { + "description": "The state of the synchronization of the token" + }, + "@syncStateCompletedDescription": { + "description": "The description of the state when the synchronization is completed" + }, + "@syncStateFailedDescription": { + "description": "The description of the state when the synchronization failed" + }, + "@syncStateNotStartedDescription": { + "description": "The description of the state when the synchronization is not started" + }, + "@syncStateSyncingDescription": { + "description": "The description of the state when the synchronization is currently syncing" + }, "@synchronizePushTokens": { "description": "Title of synchronizing push tokens in settings." }, @@ -1199,6 +1235,7 @@ "confirmation": "Confirmation", "connectionFailed": "Connection failed.", "container": "Container", + "containerDetails": "Container details", "containerSerial": "Container Serial", "containerSyncUrl": "Container Sync Url", "continueButton": "Continue", @@ -1229,7 +1266,6 @@ "dismiss": "Dismiss", "done": "Done", "edit": "Edit", - "editContainer": "Edit Container", "editLockedToken": "Please authenticate to edit the locked token.", "editToken": "Edit Token", "enablePolling": "Enable polling", @@ -1458,10 +1494,10 @@ "syncContainerFailed": "Container synchronization failed", "syncFbTokenFailed": "Synchronization failed for the following tokens, please try again:", "syncState": "Sync State", - "syncStateCompletedName": "Sync Completed", - "syncStateFailedName": "Sync Failed", - "syncStateNotStartedName": "Sync not started", - "syncStateSyncingName": "Currently Syncing", + "syncStateCompletedDescription": "Sync completed", + "syncStateFailedDescription": "Sync Failed", + "syncStateNotStartedDescription": "Sync not started", + "syncStateSyncingDescription": "Currently Syncing", "synchronizePushTokens": "Synchronize push tokens", "synchronizesTokensWithServer": "Synchronizes tokens with the privacyIDEA server.", "synchronizingTokens": "Synchronizing tokens.", diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 4d762bad..30bfa6a9 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -40,7 +40,6 @@ part 'token_container.freezed.dart'; part 'token_container.g.dart'; @Freezed(toStringOverride: false) -@SyncStateJsonConverter() class TokenContainer with _$TokenContainer { static const SERIAL = 'serial'; static const eccUtils = EccUtils(); @@ -109,7 +108,7 @@ class TokenContainer with _$TokenContainer { required Algorithms hashAlgorithm, @Default('privacyIDEA') String serverName, @Default(RolloutState.completed) RolloutState finalizationState, - @Default(SyncState.notStarted) @SyncStateJsonConverter() SyncState syncState, + @Default(SyncState.notStarted) SyncState syncState, String? passphraseQuestion, required String publicServerKey, required String publicClientKey, @@ -161,9 +160,13 @@ class TokenContainer with _$TokenContainer { privateClientKey: eccUtils.serializeECPrivateKey(keyPair.privateKey), finalizationState: RolloutState.generatingKeyPairCompleted, ); - factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); + // json["runtimeType"] == "finalized" + // ? (_$TokenContainerFromJson(json) as TokenContainerFinalized) + // .copyWith(syncState: json["syncState"] == "syncing" ? SyncState.failed : SyncState.values.byName(json["syncState"])) + // : + @override String toString() => '$runtimeType(' 'issuer: $issuer, ' @@ -198,26 +201,3 @@ class TokenContainer with _$TokenContainer { ), ); } - -class SyncStateJsonConverter extends JsonConverter { - const SyncStateJsonConverter(); - @override - SyncState? fromJson(json) { - if (json == null) return null; - final SyncState syncState; - try { - syncState = SyncState.values.byName(json); - } catch (e) { - return null; - } - return switch (syncState) { - SyncState.notStarted => SyncState.notStarted, - SyncState.syncing => SyncState.failed, - SyncState.completed => SyncState.completed, - SyncState.failed => SyncState.failed, - }; - } - - @override - toJson(object) => object?.name; -} diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart index 88fcacee..58f6f3c6 100644 --- a/lib/model/token_container.freezed.dart +++ b/lib/model/token_container.freezed.dart @@ -70,7 +70,7 @@ mixin _$TokenContainer { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -106,7 +106,7 @@ mixin _$TokenContainer { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -142,7 +142,7 @@ mixin _$TokenContainer { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -552,7 +552,7 @@ class _$TokenContainerUnfinalizedImpl extends TokenContainerUnfinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -605,7 +605,7 @@ class _$TokenContainerUnfinalizedImpl extends TokenContainerUnfinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -658,7 +658,7 @@ class _$TokenContainerUnfinalizedImpl extends TokenContainerUnfinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -801,7 +801,7 @@ abstract class _$$TokenContainerFinalizedImplCopyWith<$Res> Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -911,7 +911,7 @@ class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { required this.hashAlgorithm, this.serverName = 'privacyIDEA', this.finalizationState = RolloutState.completed, - @SyncStateJsonConverter() this.syncState = SyncState.notStarted, + this.syncState = SyncState.notStarted, this.passphraseQuestion, required this.publicServerKey, required this.publicClientKey, @@ -945,7 +945,6 @@ class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { final RolloutState finalizationState; @override @JsonKey() - @SyncStateJsonConverter() final SyncState syncState; @override final String? passphraseQuestion; @@ -1047,7 +1046,7 @@ class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -1100,7 +1099,7 @@ class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -1153,7 +1152,7 @@ class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { Algorithms hashAlgorithm, String serverName, RolloutState finalizationState, - @SyncStateJsonConverter() SyncState syncState, + SyncState syncState, String? passphraseQuestion, String publicServerKey, String publicClientKey, @@ -1231,7 +1230,7 @@ abstract class TokenContainerFinalized extends TokenContainer { required final Algorithms hashAlgorithm, final String serverName, final RolloutState finalizationState, - @SyncStateJsonConverter() final SyncState syncState, + final SyncState syncState, final String? passphraseQuestion, required final String publicServerKey, required final String publicClientKey, @@ -1259,7 +1258,6 @@ abstract class TokenContainerFinalized extends TokenContainer { String get serverName; @override RolloutState get finalizationState; - @SyncStateJsonConverter() SyncState get syncState; @override String? get passphraseQuestion; diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart index 305a8cc0..65e8a939 100644 --- a/lib/utils/customization/theme_customization.dart +++ b/lib/utils/customization/theme_customization.dart @@ -422,7 +422,7 @@ class ThemeCustomization { listTileTheme: ListTileThemeData( tileColor: Colors.transparent, titleTextStyle: TextStyle(color: tilePrimaryColor), - subtitleTextStyle: TextStyle(color: tileSubtitleColor), + subtitleTextStyle: TextStyle(color: tileSubtitleColor, fontSize: 14), //bodyMedium fontSize iconColor: tileIconColor, ), colorScheme: brightness == Brightness.light diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index 53f29992..f29c9d08 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -100,7 +100,13 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler _eccUtils = _eccUtilsOverride ?? eccUtils; Logger.warning('Building containerProvider'); - final initState = await _repo.loadContainerState(); + var initState = await _repo.loadContainerState(); + final containerList = initState.containerList.map((c) { + if (c is! TokenContainerFinalized) return c; + final fixedSyncState = c.syncState == SyncState.syncing ? SyncState.failed : c.syncState; + return c.copyWith(syncState: fixedSyncState); + }).toList(); + initState = initState.copyWith(containerList: containerList); for (var container in initState.containerList.whereType()) { finalize(container, isManually: false); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index b5228250..391dab50 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_container_notifier.dart'; // ************************************************************************** String _$tokenContainerNotifierHash() => - r'ce579b9e2f02bf34af021a6bcb567f0889f8f5ed'; + r'ab2f1d98b00a7cbe7c295c517f1b70a55d0c088a'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart index 50c4f047..523eed62 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart @@ -105,15 +105,10 @@ class _TokenFolderExpandableState extends ConsumerState w Positioned.fill( child: Container( padding: const EdgeInsets.symmetric(vertical: borderRadius), - margin: const EdgeInsets.only(bottom: 8, left: 14), + margin: const EdgeInsets.only(bottom: 8, left: 14, right: 14), decoration: BoxDecoration( color: isExpanded ? Theme.of(context).scaffoldBackgroundColor : Colors.transparent, - borderRadius: isExpanded - ? const BorderRadius.only( - topLeft: Radius.circular(borderRadius), - bottomLeft: Radius.circular(borderRadius), - ) - : const BorderRadius.all(Radius.circular(borderRadius)), + borderRadius: const BorderRadius.all(Radius.circular(borderRadius)), boxShadow: [ if (isExpanded) BoxShadow( diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart index 200f1745..a159b62c 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart @@ -42,7 +42,7 @@ class TokenFolderExpandableBody extends StatelessWidget { @override Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.only(bottom: 4, left: 14), + padding: const EdgeInsets.fromLTRB(14, 0, 14, 4), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart index 5bbf55db..8065301a 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart @@ -72,8 +72,9 @@ class _TokenFolderExpandableHeaderState extends ConsumerState( onWillAcceptWithDetails: (details) { if (details.data.folderId != widget.folder.folderId) { @@ -147,10 +148,13 @@ class _TokenFolderExpandableHeaderState extends ConsumerState()?.lockColor, - size: constraints.maxHeight / 2.1, - shadows: [ - Shadow( - color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), - offset: const Offset(0.5, 0.5), - blurRadius: 2, - ) - ], - ), - ); - }, - ), + child: Padding( + padding: const EdgeInsets.all(2), + child: Center( + child: Stack( + alignment: Alignment.center, + children: [ + Icon( + weight: 0.1, + isExpanded ? FontAwesomeIcons.folderOpen : FontAwesomeIcons.solidFolderClosed, + color: Theme.of(context).listTileTheme.iconColor, ), - ], + if (isLocked) + Positioned.fill( + child: LayoutBuilder( + builder: (context, constraints) { + return Container( + padding: EdgeInsets.only(left: isExpanded ? 2 : 0.0, top: 6), + child: Center( + child: Transform( + transform: isExpanded + ? Matrix4.fromList([ + 01.0, 0.00, 0, 0, // + -0.5, 0.85, 0, 0, // + 00.0, 0.00, 1, 0, // + 05.5, 2.00, 0, 1, // + ]) + : Matrix4.identity(), + child: Icon( + isExpanded ? MdiIcons.lockOpenVariant : MdiIcons.lock, + color: Theme.of(context).extension()?.lockColor, + size: constraints.maxHeight / 2.1, + shadows: [ + Shadow( + color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), + offset: const Offset(0.5, 0.5), + blurRadius: 2, + ) + ], + ), + ), + ), + ); + }, + ), + ), + ], + ), ), ), ); diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart index 91b532a8..1887e67f 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart @@ -69,7 +69,7 @@ class MainViewTokensList extends ConsumerStatefulWidget { final currentIsExpandedFolder = sortable is TokenFolder && sortable.isExpanded; final folderTokens = sortable is TokenFolder ? sortables.where((s) => s is Token && s.folderId == sortable.folderId).cast().toList() : null; if (hidePushTokens) folderTokens?.removeWhere((t) => t is PushToken); - if (folderTokens?.isEmpty == true) continue; + if (filter != null && folderTokens?.isEmpty == true) continue; // 1. Add a divider if the current sortable is not the one which is dragged // 2. Don't add a divider if the current sortable is the first // 3. Don't add a divider after an expanded folder diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 7373d8ae..84925afd 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -136,7 +136,7 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState '${AppLocalizations.of(context)!.validFor}:', - DayPasswordTokenViewMode.VALIDUNTIL => '${AppLocalizations.of(context)!.validUntil}:', - }, - style: Theme.of(context).listTileTheme.subtitleTextStyle, - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - softWrap: false, + SizedBox( + height: Theme.of(context).listTileTheme.subtitleTextStyle!.fontSize! * 1.5, + child: Center( + child: Text( + switch (widget.token.viewMode) { + DayPasswordTokenViewMode.VALIDFOR => '${AppLocalizations.of(context)!.validFor}:', + DayPasswordTokenViewMode.VALIDUNTIL => '${AppLocalizations.of(context)!.validUntil}:', + }, + style: Theme.of(context).listTileTheme.subtitleTextStyle, + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + softWrap: false, + ), ), ), - Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: double.infinity, + maxHeight: Theme.of(context).textTheme.bodyLarge!.fontSize! * 3, + minHeight: Theme.of(context).textTheme.bodyLarge!.fontSize! * 3, + ), + child: Center( child: Text( switch (widget.token.viewMode) { DayPasswordTokenViewMode.VALIDFOR => durationString, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 537a1703..1564c5cd 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -113,7 +113,7 @@ class TokenWidgetBase extends ConsumerWidget { opacity: 0.05, child: switch (syncState) { SyncState.notStarted => const Icon(Icons.sync), - SyncState.syncing => const Icon(Icons.sync), + SyncState.syncing => const Icon(Icons.cloud_sync_outlined), SyncState.failed => const Icon(Icons.cloud_off_outlined), SyncState.completed => const Icon(Icons.cloud_done_outlined), }, diff --git a/pubspec.lock b/pubspec.lock index ad80ff0c..17ce767d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -767,6 +767,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f" + url: "https://pub.dev" + source: hosted + version: "10.7.0" freezed: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5491750d..0c217e90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,6 +97,7 @@ dependencies: image_picker: ^1.1.2 flutter_zxing: ^1.7.0 zxing2: ^0.2.3 + font_awesome_flutter: ^10.7.0 dev_dependencies: