From 0e9785ff0a89cf8943cd4d8111590216d5b94dbb Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:51:30 +0200 Subject: [PATCH] fixed filtered main view tokens list --- .../token_container_state.dart | 2 +- lib/model/riverpod_states/token_filter.dart | 27 +++++ .../free_otp_plus_import_file_processor.dart | 2 +- .../delete_container_action.dart | 1 - lib/views/main_view/main_view.dart | 18 ++- .../drag_target_divider.dart | 24 ++-- .../token_folder_expandable.dart | 12 +- .../token_folder_expandable_body.dart | 2 +- .../folder_widgets/token_folder_widget.dart | 18 ++- .../main_view_tokens_list.dart | 103 ++++++++++-------- .../main_view_tokens_list_filtered.dart | 95 ++++++++-------- .../sortable_widget_builder.dart | 65 +++++------ .../token_widgets/token_widget_builder.dart | 2 +- .../widgets/push_tokens_view_list.dart | 42 ++++--- .../qr_scanner_widget.dart | 1 - lib/widgets/default_refresh_indicator.dart | 4 +- .../push_request_dialog.dart | 2 +- lib/widgets/push_request_listener.dart | 1 - 18 files changed, 244 insertions(+), 177 deletions(-) diff --git a/lib/model/riverpod_states/token_container_state.dart b/lib/model/riverpod_states/token_container_state.dart index 4b6d87ca9..b7aebe0f5 100644 --- a/lib/model/riverpod_states/token_container_state.dart +++ b/lib/model/riverpod_states/token_container_state.dart @@ -34,7 +34,7 @@ class TokenContainerState with _$TokenContainerState { required List containerList, }) = _TokenContainerState; - bool get hasFinalizedContainers => containerList.every((container) => container is TokenContainerFinalized); + bool get hasFinalizedContainers => containerList.any((container) => container is TokenContainerFinalized); TokenContainer? containerOf(String containerSerial) => containerList.firstWhereOrNull((container) => container.serial == containerSerial); static TokenContainerState fromJsonStringList(List jsonStrings) { diff --git a/lib/model/riverpod_states/token_filter.dart b/lib/model/riverpod_states/token_filter.dart index 8d2b923cd..01045b1b8 100644 --- a/lib/model/riverpod_states/token_filter.dart +++ b/lib/model/riverpod_states/token_filter.dart @@ -17,6 +17,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'package:privacyidea_authenticator/model/mixins/sortable_mixin.dart'; +import 'package:privacyidea_authenticator/model/token_folder.dart'; + import '../tokens/push_token.dart'; import '../tokens/token.dart'; @@ -38,4 +41,28 @@ class TokenFilter { } return filteredTokens; } + + List filterSortables(List sortables) { + final filteredSortables = []; + final RegExp regExp; + try { + regExp = RegExp(searchQuery, caseSensitive: false); + } catch (e) { + return []; + } + for (final sortable in sortables) { + if (sortable is TokenFolder) { + filteredSortables.add(sortable); + continue; + } + if (sortable is Token && + (regExp.hasMatch(sortable.label) || + regExp.hasMatch(sortable.issuer) || + sortable is PushToken && regExp.hasMatch(sortable.serial) || + regExp.hasMatch(sortable.type))) { + filteredSortables.add(sortable); + } + } + return filteredSortables; + } } diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index ff514debf..108372580 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -23,8 +23,8 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/enums/encodings.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; diff --git a/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart b/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart index ecf71c8b3..8eabf6987 100644 --- a/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart +++ b/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart @@ -29,7 +29,6 @@ import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_ import '../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../widgets/elevated_delete_button.dart'; import '../../../main_view/main_view_widgets/token_widgets/slideable_action.dart'; -import '../../../view_interface.dart'; class DeleteContainerAction extends ConsumerSlideableAction { final TokenContainer container; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 5a9b1b668..325634594 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -33,10 +33,10 @@ import '../view_interface.dart'; import 'main_view_widgets/app_bar_item.dart'; import 'main_view_widgets/connectivity_listener.dart'; import 'main_view_widgets/expandable_appbar.dart'; +import 'main_view_widgets/main_view_background_image.dart'; import 'main_view_widgets/main_view_navigation_bar.dart'; import 'main_view_widgets/main_view_tokens_list.dart'; import 'main_view_widgets/main_view_tokens_list_filtered.dart'; -import 'main_view_widgets/main_view_background_image.dart'; export '../../views/main_view/main_view.dart'; @@ -117,15 +117,13 @@ class _MainViewState extends ConsumerState { ]), body: ConnectivityListener( child: StatusBar( - child: !hasFilter - ? Stack( - children: [ - MainViewBackgroundImage(appImage: widget.appImage), - MainViewTokensList(nestedScrollViewKey: globalKey), - MainViewNavigationBar(), - ], - ) - : const MainViewTokensListFiltered(), + child: Stack( + children: [ + MainViewBackgroundImage(appImage: widget.appImage), + hasFilter ? const MainViewTokensListFiltered() : MainViewTokensList(nestedScrollViewKey: globalKey), + if (!hasFilter) MainViewNavigationBar(), + ], + ), ), ), ), diff --git a/lib/views/main_view/main_view_widgets/drag_target_divider.dart b/lib/views/main_view/main_view_widgets/drag_target_divider.dart index 716e7ba51..21a38a4a9 100644 --- a/lib/views/main_view/main_view_widgets/drag_target_divider.dart +++ b/lib/views/main_view/main_view_widgets/drag_target_divider.dart @@ -35,22 +35,20 @@ class DragTargetDivider extends ConsumerStatefulWidget final SortableMixin? nextSortable; final double dividerBaseHeight; final double dividerExpandedHeight; - final double bottomPaddingIfLast; + final double bottomPadding; final double opacity; final bool isExpandalbe; - final bool isLastDivider; const DragTargetDivider({ super.key, required this.dependingFolder, required this.previousSortable, required this.nextSortable, - this.bottomPaddingIfLast = 0, + this.bottomPadding = 0, this.dividerBaseHeight = 1.5, this.dividerExpandedHeight = 40, this.opacity = 1, this.isExpandalbe = true, - this.isLastDivider = false, }); @override @@ -105,7 +103,7 @@ class _DragTargetDividerState extends ConsumerState Opacity( opacity: opacity, - child: Container( - height: dividerHeight, - decoration: BoxDecoration( - color: Theme.of(context).dividerColor, - borderRadius: BorderRadius.circular(dividerHeight / 4), + child: Padding( + padding: padding ?? EdgeInsets.zero, + child: Container( + height: dividerHeight, + decoration: BoxDecoration( + color: Theme.of(context).dividerColor, + borderRadius: BorderRadius.circular(dividerHeight / 4), + ), + margin: margin, ), - padding: padding, - margin: margin, ), ); } 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 01fb1c4dd..94ddbc4aa 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 @@ -21,27 +21,26 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/token_folder_extension.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; import '../../../../model/riverpod_states/token_filter.dart'; import '../../../../model/token_folder.dart'; -import '../../../../model/tokens/push_token.dart'; import '../../../../utils/globals.dart'; -import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import 'token_folder_expandable_widgets/token_folder_expandable_header.dart'; import 'token_folder_expandable_widgets/token_folder_expandable_body.dart'; class TokenFolderExpandable extends ConsumerStatefulWidget { final TokenFolder folder; + final List folderTokens; final TokenFilter? filter; final bool? expandOverride; const TokenFolderExpandable({ super.key, required this.folder, + required this.folderTokens, this.filter, this.expandOverride, }); @@ -87,10 +86,7 @@ class _TokenFolderExpandableState extends ConsumerState w @override Widget build(BuildContext context) { - final hidePushTokens = ref.watch(hidePushTokensProvider); - final folderTokens = ref.watch(tokenProvider.select((state) => state.tokensInFolder(widget.folder).whereNotType(hidePushTokens ? [PushToken] : []))); - final tokensFiltered = widget.filter?.filterTokens(folderTokens) ?? folderTokens; - if (tokensFiltered.isEmpty) return const SizedBox(); + final tokensFiltered = widget.folderTokens; tokensFiltered.sort((a, b) => a.compareTo(b)); final draggingSortable = ref.watch(draggingSortableProvider); if (widget.expandOverride == null) { 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 3de065a36..200f17452 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 @@ -55,7 +55,7 @@ class TokenFolderExpandableBody extends StatelessWidget { previousSortable: (i - 1) < 0 ? null : tokens[i - 1], nextSortable: tokens[i], ), - TokenWidgetBuilder.fromToken(tokens[i]), + TokenWidgetBuilder.fromToken(token: tokens[i]), ], if (tokens.isNotEmpty && draggingSortable is Token) isFilterd diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart index dea62c8d9..c9bda2497 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart @@ -22,7 +22,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../model/riverpod_states/token_filter.dart'; import '../../../../model/token_folder.dart'; +import '../../../../model/tokens/token.dart'; import '../../../../utils/logger.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; @@ -30,8 +32,15 @@ import 'token_folder_expandable.dart'; class TokenFolderWidget extends ConsumerWidget { final TokenFolder folder; + final List folderTokens; + final TokenFilter? filter; - const TokenFolderWidget(this.folder, {super.key}); + const TokenFolderWidget({ + required this.folder, + required this.folderTokens, + this.filter, + super.key, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -80,12 +89,17 @@ class TokenFolderWidget extends ConsumerWidget { padding: const EdgeInsets.only(top: 4), child: TokenFolderExpandable( folder: folder, + folderTokens: folderTokens, key: Key('TokenFolderExpandable#${folder.folderId}'), ), ), ) : (draggingFolder == folder) ? const SizedBox() - : TokenFolderExpandable(folder: folder); + : TokenFolderExpandable( + folder: folder, + folderTokens: folderTokens, + filter: filter, + ); } } 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 b7dca771f..91b532a88 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 @@ -21,9 +21,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/riverpod_states/settings_state.dart'; +import '../../../model/riverpod_states/token_filter.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/push_token.dart'; import '../../../model/tokens/token.dart'; @@ -34,8 +36,8 @@ import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; import '../../../widgets/introduction_widgets/token_introduction.dart'; import 'drag_target_divider.dart'; +import 'folder_widgets/token_folder_widget.dart'; import 'no_token_screen.dart'; -import 'sortable_widget_builder.dart'; class MainViewTokensList extends ConsumerStatefulWidget { final GlobalKey nestedScrollViewKey; @@ -45,21 +47,33 @@ class MainViewTokensList extends ConsumerStatefulWidget { @override ConsumerState createState() => _MainViewTokensListState(); - static List buildSortableWidgets(List sortables, SortableMixin? draggingSortable) { + static List buildSortableWidgets({ + required List sortables, + required SortableMixin? draggingSortable, + bool hidePushTokens = false, + bool isPushTokensView = false, + TokenFilter? filter, + }) { List widgets = []; if (sortables.isEmpty) return []; sortables.sort((a, b) => a.compareTo(b)); + sortables = filter?.filterSortables(sortables) ?? sortables; bool introductionAdded = false; for (var i = 0; i < sortables.length; i++) { + final sortable = sortables[i]; + if (hidePushTokens && sortable is PushToken) continue; + if (sortable is Token && sortable.folderId != null && !isPushTokensView) continue; final isFirst = i == 0; - final isDraggingTheCurrent = draggingSortable == sortables[i]; + final isDraggingTheCurrent = draggingSortable == sortable; final previousWasExpandedFolder = i > 0 && sortables[i - 1] is TokenFolder && (sortables[i - 1] as TokenFolder).isExpanded; - final currentIsExpandedFolder = sortables[i] is TokenFolder && (sortables[i] as TokenFolder).isExpanded; + 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; // 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 // 4. Ignore 2. and 3. if there is a sortable that is dragged - // 5. Do not add a divider before a folder // 1 2 3 4 if (!isDraggingTheCurrent && ((!isFirst && !previousWasExpandedFolder) || draggingSortable != null)) { widgets.add( @@ -68,23 +82,46 @@ class MainViewTokensList extends ConsumerStatefulWidget { opacity: currentIsExpandedFolder && draggingSortable == null ? 0 : 1, dependingFolder: null, previousSortable: i == 0 ? null : sortables.elementAtOrNull(i - 1), - nextSortable: sortables[i], + nextSortable: sortable, ), ); } - if (introductionAdded == false && sortables[i] is Token) { + + if (sortable is Token) { widgets.add( - TokenIntroduction( - key: Key('mainview_introduction_${sortables[i].runtimeType}${sortables[i].sortIndex}'), - child: SortableWidgetBuilder.fromSortable(sortables[i]), - ), + introductionAdded + ? TokenWidgetBuilder.fromToken(token: sortable) + : TokenIntroduction( + key: Key('mainview_introduction_${sortable.runtimeType}${sortable.sortIndex}'), + child: TokenWidgetBuilder.fromToken(token: sortable), + ), ); introductionAdded = true; - } else { - widgets.add(SortableWidgetBuilder.fromSortable(sortables[i], key: Key('mainview_${sortables[i].runtimeType}${sortables[i].hashCode}'))); + continue; } - } + if (sortable is TokenFolder) { + widgets.add( + TokenFolderWidget( + folder: sortable, + folderTokens: folderTokens!, + key: Key('mainview_${sortable.runtimeType}${sortable.hashCode}'), + filter: filter, + ), + ); + continue; + } + } + widgets.add( + (draggingSortable != null) + ? DragTargetDivider( + dependingFolder: null, + previousSortable: sortables.last, + nextSortable: null, + bottomPadding: 110, + ) + : const SizedBox(height: 110), + ); return widgets; } } @@ -92,28 +129,13 @@ class MainViewTokensList extends ConsumerStatefulWidget { class _MainViewTokensListState extends ConsumerState { final listViewKey = GlobalKey(); final scrollController = ScrollController(); - Duration? lastTimeStamp; @override Widget build(BuildContext context) { final draggingSortable = ref.watch(draggingSortableProvider); - final allSortables = ref.watch(sortablesProvider); - final allowToRefresh = allSortables.any((element) => element is PushToken); + final sortables = ref.watch(sortablesProvider); final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; - final filterPushTokens = hidePushTokens && allowToRefresh; - final showSortables = []; // List of sortables that should be shown in the list - - for (var element in allSortables) { - if (element is! Token) { - showSortables.add(element); - continue; - } - if (element is PushToken && filterPushTokens == true) continue; - if (element.folderId != null) continue; - showSortables.add(element); - } - - if ((showSortables.isEmpty)) return const NoTokenScreen(); + if ((sortables.isEmpty)) return const NoTokenScreen(); return Stack( children: [ @@ -127,10 +149,9 @@ class _MainViewTokensListState extends ConsumerState { opacity: 0, child: DragTargetDivider( dependingFolder: null, - previousSortable: showSortables.last, + previousSortable: sortables.last, nextSortable: null, - isLastDivider: true, - bottomPaddingIfLast: 0, + bottomPadding: 1, ), ), ), @@ -149,16 +170,7 @@ class _MainViewTokensListState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), - (draggingSortable != null) - ? DragTargetDivider( - dependingFolder: null, - previousSortable: showSortables.last, - nextSortable: null, - isLastDivider: true, - bottomPaddingIfLast: 80, - ) - : const SizedBox(height: 80), + ...MainViewTokensList.buildSortableWidgets(sortables: sortables, draggingSortable: draggingSortable, hidePushTokens: hidePushTokens), ], ), ), @@ -167,7 +179,4 @@ class _MainViewTokensListState extends ConsumerState { ], ); } - - ScrollPhysics _getScrollPhysics(bool allowToRefresh) => - allowToRefresh ? const AlwaysScrollableScrollPhysics(parent: ClampingScrollPhysics()) : const BouncingScrollPhysics(); } diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index e8411faa8..08b403951 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -19,19 +19,15 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/token_folder_extension.dart'; -import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/drag_target_divider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../model/mixins/sortable_mixin.dart'; -import '../../../model/riverpod_states/token_filter.dart'; -import '../../../model/token_folder.dart'; -import '../../../model/tokens/token.dart'; -import '../../../utils/logger.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; -import 'folder_widgets/token_folder_expandable.dart'; -import 'token_widgets/token_widget_builder.dart'; + +import 'main_view_tokens_list.dart'; class MainViewTokensListFiltered extends ConsumerWidget { const MainViewTokensListFiltered({super.key}); @@ -53,44 +49,57 @@ class MainViewTokensListFiltered extends ConsumerWidget { if (filter == null) return []; final tokenFolders = ref.watch(tokenFolderProvider).folders; final allTokens = ref.watch(tokenProvider).tokens; - final tokensInFolder = allTokens.inFolder(); - final tokensInNoFolder = allTokens.inNoFolder(); - final filtered = filter.filterTokens(tokensInNoFolder); - List sortables = [...tokenFolders, ...filtered]; + // final tokensInFolder = allTokens.inFolder(); + // final tokensInNoFolder = allTokens.inNoFolder(); + final filteredTokens = filter.filterTokens(allTokens); + List sortables = [...tokenFolders, ...filteredTokens]; + final draggingSortable = ref.watch(draggingSortableProvider); sortables.sort((a, b) => a.compareTo(b)); - final List widgets = []; - for (int i = 0; i < sortables.length; i++) { - final sortable = sortables[i]; - final nextIsFolder = i < sortables.length - 1 && sortables[i + 1] is TokenFolder; - if (sortable is Token) { - widgets.add(TokenWidgetBuilder.fromToken(sortable)); - if (i != sortables.length - 1) { - widgets.add(DefaultDivider(opacity: nextIsFolder ? 0 : 1)); - } - } else if (sortable is TokenFolder) { - widgets.addAll(_buildFilteredFolders(ref: ref, folder: sortable, filter: filter, allFolderTokens: tokensInFolder, isLast: i == sortables.length - 1)); - } - } + final List widgets = MainViewTokensList.buildSortableWidgets( + sortables: sortables, + draggingSortable: draggingSortable, + filter: filter, + hidePushTokens: ref.watch(hidePushTokensProvider), + ); + // for (int i = 0; i < sortables.length; i++) { + // final sortable = sortables[i]; + // final nextIsFolder = i < sortables.length - 1 && sortables[i + 1] is TokenFolder; + // if (sortable is Token) { + // widgets.add(TokenWidgetBuilder.fromToken(sortable)); + // if (i != sortables.length - 1) { + // widgets.add(DefaultDivider(opacity: nextIsFolder ? 0 : 1)); + // } + // } else if (sortable is TokenFolder) { + // widgets.addAll(_buildFilteredFolders(ref: ref, folder: sortable, filter: filter, allFolderTokens: tokensInFolder, isLast: i == sortables.length - 1)); + // } + // } return widgets; } - List _buildFilteredFolders({ - required WidgetRef ref, - required TokenFolder folder, - required TokenFilter filter, - required List allFolderTokens, - required bool isLast, - }) { - final folderTokens = allFolderTokens.inFolder(folder); - final filtered = filter.filterTokens(folderTokens); - if (filtered.isEmpty) return []; - // Auto expand if search query is not empty and folder is not locked. - final expanded = filter.searchQuery.isNotEmpty && !folder.isLocked ? true : null; - Logger.warning('Expanded: $expanded'); - return [ - TokenFolderExpandable(folder: folder, filter: filter, expandOverride: expanded, key: ValueKey('filteredFolder:${folder.folderId}')), - if (!isLast) const Divider(), - ]; - } + // List _buildFilteredFolders({ + // required WidgetRef ref, + // required TokenFolder folder, + // required TokenFilter filter, + // required List allFolderTokens, + // required bool isLast, + // }) { + // final folderTokens = allFolderTokens.inFolder(folder); + // final filtered = filter.filterTokens(folderTokens); + // if (filtered.isEmpty) return []; + // // Auto expand if search query is not empty and folder is not locked. + // final expanded = filter.searchQuery.isNotEmpty && !folder.isLocked ? true : null; + // Logger.warning('Expanded: $expanded'); + // return [ + // TokenFolderExpandable( + // folder: folder, + // folderTokens: , + // filter: filter, + // expandOverride: expanded, + // key: ValueKey( + // 'filteredFolder:${folder.folderId}', + // )), + // if (!isLast) const Divider(), + // ]; + // } } diff --git a/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart b/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart index 0dfaec32d..026947713 100644 --- a/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart +++ b/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart @@ -1,34 +1,35 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 NetKnights GmbH - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import 'package:flutter/material.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 NetKnights GmbH +// * +// * Licensed under the Apache License, Version 2.0 (the 'License'); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an 'AS IS' BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// import 'package:flutter/material.dart'; +// import 'package:privacyidea_authenticator/model/riverpod_states/token_filter.dart'; -import '../../../model/mixins/sortable_mixin.dart'; -import '../../../model/token_folder.dart'; -import '../../../model/tokens/token.dart'; -import 'folder_widgets/token_folder_widget.dart'; -import 'token_widgets/token_widget_builder.dart'; +// import '../../../model/mixins/sortable_mixin.dart'; +// import '../../../model/token_folder.dart'; +// import '../../../model/tokens/token.dart'; +// import 'folder_widgets/token_folder_widget.dart'; +// import 'token_widgets/token_widget_builder.dart'; -abstract class SortableWidgetBuilder { - static Widget fromSortable(SortableMixin sortable, {Key? key}) { - if (sortable is TokenFolder) return TokenFolderWidget(sortable, key: key); - if (sortable is Token) return TokenWidgetBuilder.fromToken(sortable, key: key); - throw UnimplementedError('Sortable type (${sortable.runtimeType}) not supported'); - } -} +// abstract class SortableWidgetBuilder { +// static Widget fromSortable(SortableMixin sortable, {Key? key, TokenFilter? filter}) { +// if (sortable is TokenFolder) return TokenFolderWidget(folder: sortable, key: key, filter: filter); +// if (sortable is Token) return TokenWidgetBuilder.fromToken(token: sortable, key: key); +// throw UnimplementedError('Sortable type (${sortable.runtimeType}) not supported'); +// } +// } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart index 6824c2c3c..2d1ee16e8 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart @@ -36,7 +36,7 @@ import 'totp_token_widgets/totp_token_widget.dart'; import 'totp_token_widgets/totp_token_widget_tile.dart'; abstract class TokenWidgetBuilder { - static TokenWidget fromToken(Token token, {Key? key}) { + static TokenWidget fromToken({required Token token, Key? key}) { return switch (token.runtimeType) { const (TOTPToken) => TOTPTokenWidget(token as TOTPToken, key: key), const (HOTPToken) => HOTPTokenWidget(token as HOTPToken, key: key), diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index 4a68558c9..5e7746618 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -21,11 +21,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import '../../../model/mixins/sortable_mixin.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; +import '../../main_view/main_view_widgets/drag_target_divider.dart'; import '../../main_view/main_view_widgets/main_view_tokens_list.dart'; class PushTokensViwList extends ConsumerStatefulWidget { @@ -39,18 +39,34 @@ class _PushTokensViwListState extends ConsumerState { final listViewKey = GlobalKey(); final scrollController = ScrollController(); - Duration? lastTimeStamp; - @override Widget build(BuildContext context) { final tokenState = ref.watch(tokenProvider); final pushTokens = tokenState.pushTokens; - final allowToRefresh = pushTokens.isNotEmpty; final draggingSortable = ref.watch(draggingSortableProvider); - List sortables = [...pushTokens]; return Stack( children: [ + Column( + children: [ + Flexible( + child: DefaultRefreshIndicator( + child: SizedBox( + height: 9999, + child: Opacity( + opacity: 0, + child: DragTargetDivider( + dependingFolder: null, + previousSortable: pushTokens.lastOrNull, + nextSortable: null, + bottomPadding: 0, + ), + ), + ), + ), + ), + ], + ), DefaultRefreshIndicator( listViewKey: listViewKey, scrollController: scrollController, @@ -59,15 +75,13 @@ class _PushTokensViwListState extends ConsumerState { listViewKey: listViewKey, itemIsDragged: draggingSortable != null, scrollController: scrollController, - child: CustomScrollView( - key: listViewKey, - physics: allowToRefresh ? const AlwaysScrollableScrollPhysics() : null, - controller: scrollController, - slivers: [ - SliverList( - delegate: SliverChildListDelegate( - [...MainViewTokensList.buildSortableWidgets(sortables, draggingSortable)], - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...MainViewTokensList.buildSortableWidgets( + sortables: pushTokens, + draggingSortable: draggingSortable, + isPushTokensView: true, ), ], ), diff --git a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart index 13935527a..7d2d9ef3f 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart @@ -19,7 +19,6 @@ */ import 'package:flutter/material.dart'; - import 'package:flutter_zxing/flutter_zxing.dart'; class QRScannerWidget extends StatefulWidget { diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index ed3a5eda8..d9fb0b82a 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -27,7 +27,9 @@ class _DefaultRefreshIndicatorState extends ConsumerState