mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 09:15:48 -06:00
688 lines
26 KiB
Dart
688 lines
26 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:defer_pointer/defer_pointer.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:paperless_api/paperless_api.dart';
|
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
|
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_view_changed_dialog.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_views_widget.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
|
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
|
import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart';
|
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
|
import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart';
|
|
import 'package:sliver_tools/sliver_tools.dart';
|
|
|
|
class DocumentFilterIntent {
|
|
final DocumentFilter? filter;
|
|
final bool shouldReset;
|
|
|
|
DocumentFilterIntent({
|
|
this.filter,
|
|
this.shouldReset = false,
|
|
});
|
|
}
|
|
|
|
class DocumentsPage extends StatefulWidget {
|
|
const DocumentsPage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<DocumentsPage> createState() => _DocumentsPageState();
|
|
}
|
|
|
|
class _DocumentsPageState extends State<DocumentsPage> {
|
|
final SliverOverlapAbsorberHandle searchBarHandle =
|
|
SliverOverlapAbsorberHandle();
|
|
|
|
final SliverOverlapAbsorberHandle savedViewsHandle =
|
|
SliverOverlapAbsorberHandle();
|
|
|
|
final _nestedScrollViewKey = GlobalKey<NestedScrollViewState>();
|
|
|
|
final _savedViewsExpansionController = ExpansionTileController();
|
|
bool _showExtendedFab = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// context.read<PendingTasksNotifier>().addListener(_onTasksChanged);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_nestedScrollViewKey.currentState!.innerController
|
|
.addListener(_scrollExtentChangedListener);
|
|
});
|
|
}
|
|
|
|
void _onTasksChanged() {
|
|
final notifier = context.read<PendingTasksNotifier>();
|
|
final tasks = notifier.value;
|
|
final finishedTasks = tasks.values.where((element) => element.isSuccess);
|
|
if (finishedTasks.isNotEmpty) {
|
|
showSnackBar(
|
|
context,
|
|
S.of(context)!.newDocumentAvailable,
|
|
action: SnackBarActionConfig(
|
|
label: S.of(context)!.reload,
|
|
onPressed: () {
|
|
// finishedTasks.forEach((task) {
|
|
// notifier.acknowledgeTasks([finishedTasks]);
|
|
// });
|
|
context.read<DocumentsCubit>().reload();
|
|
},
|
|
),
|
|
duration: const Duration(seconds: 10),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _reloadData() async {
|
|
final user = context.read<LocalUserAccount>().paperlessUser;
|
|
try {
|
|
await Future.wait([
|
|
context.read<DocumentsCubit>().reload(),
|
|
if (user.canViewSavedViews) context.read<SavedViewCubit>().reload(),
|
|
if (user.canViewTags) context.read<LabelCubit>().reloadTags(),
|
|
if (user.canViewCorrespondents)
|
|
context.read<LabelCubit>().reloadCorrespondents(),
|
|
if (user.canViewDocumentTypes)
|
|
context.read<LabelCubit>().reloadDocumentTypes(),
|
|
if (user.canViewStoragePaths)
|
|
context.read<LabelCubit>().reloadStoragePaths(),
|
|
]);
|
|
} catch (error, stackTrace) {
|
|
showGenericError(context, error, stackTrace);
|
|
}
|
|
}
|
|
|
|
void _scrollExtentChangedListener() {
|
|
const threshold = 400;
|
|
final offset =
|
|
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
|
if (offset < threshold && _showExtendedFab == false) {
|
|
setState(() {
|
|
_showExtendedFab = true;
|
|
});
|
|
} else if (offset >= threshold && _showExtendedFab == true) {
|
|
setState(() {
|
|
_showExtendedFab = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nestedScrollViewKey.currentState?.innerController
|
|
.removeListener(_scrollExtentChangedListener);
|
|
// context.read<PendingTasksNotifier>().removeListener(_onTasksChanged);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
|
listenWhen: (previous, current) =>
|
|
previous != ConnectivityState.connected &&
|
|
current == ConnectivityState.connected,
|
|
listener: (context, state) {
|
|
_reloadData();
|
|
},
|
|
builder: (context, connectivityState) {
|
|
return SafeArea(
|
|
top: true,
|
|
child: Scaffold(
|
|
drawer: const AppDrawer(),
|
|
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
builder: (context, state) {
|
|
final show = state.selection.isEmpty;
|
|
final canReset = state.filter.appliedFiltersCount > 0;
|
|
if (show) {
|
|
return Column(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
DeferredPointerHandler(
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
FloatingActionButton.extended(
|
|
extendedPadding: _showExtendedFab
|
|
? null
|
|
: const EdgeInsets.symmetric(horizontal: 16),
|
|
heroTag: "fab_documents_page_filter",
|
|
label: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 150),
|
|
transitionBuilder: (child, animation) {
|
|
return FadeTransition(
|
|
opacity: animation,
|
|
child: SizeTransition(
|
|
sizeFactor: animation,
|
|
axis: Axis.horizontal,
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: _showExtendedFab
|
|
? Row(
|
|
children: [
|
|
const Icon(Icons.filter_alt_outlined),
|
|
const SizedBox(width: 8),
|
|
Text(S.of(context)!.filterDocuments),
|
|
],
|
|
)
|
|
: const Icon(Icons.filter_alt_outlined),
|
|
),
|
|
onPressed: _openDocumentFilter,
|
|
),
|
|
if (canReset)
|
|
Positioned(
|
|
top: -20,
|
|
right: -8,
|
|
child: DeferPointer(
|
|
paintOnTop: true,
|
|
child: Material(
|
|
color: Theme.of(context).colorScheme.error,
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(8),
|
|
onTap: () {
|
|
HapticFeedback.mediumImpact();
|
|
_onResetFilter();
|
|
},
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
if (_showExtendedFab)
|
|
Text(
|
|
"Reset (${state.filter.appliedFiltersCount})",
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.labelLarge
|
|
?.copyWith(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onError,
|
|
),
|
|
).padded()
|
|
else
|
|
Icon(
|
|
Icons.replay,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onError,
|
|
).padded(4),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
return const SizedBox.shrink();
|
|
}
|
|
},
|
|
),
|
|
resizeToAvoidBottomInset: true,
|
|
body: WillPopScope(
|
|
onWillPop: () async {
|
|
final cubit = context.read<DocumentsCubit>();
|
|
if (cubit.state.selection.isNotEmpty) {
|
|
cubit.resetSelection();
|
|
return false;
|
|
}
|
|
if (cubit.state.filter.appliedFiltersCount > 0 ||
|
|
cubit.state.filter.selectedView != null) {
|
|
await _onResetFilter();
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
child: NestedScrollView(
|
|
key: _nestedScrollViewKey,
|
|
floatHeaderSlivers: true,
|
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
|
SliverOverlapAbsorber(
|
|
handle: searchBarHandle,
|
|
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
builder: (context, state) {
|
|
if (state.selection.isEmpty) {
|
|
return SliverSearchBar(
|
|
titleText: S.of(context)!.documents,
|
|
);
|
|
} else {
|
|
return DocumentSelectionSliverAppBar(
|
|
state: state,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
SliverOverlapAbsorber(
|
|
handle: savedViewsHandle,
|
|
sliver: SliverPinnedHeader(
|
|
child: Material(
|
|
child: _buildViewActions(),
|
|
elevation: 2,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
body: _buildDocumentsTab(
|
|
connectivityState,
|
|
context,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildDocumentsTab(
|
|
ConnectivityState connectivityState,
|
|
BuildContext context,
|
|
) {
|
|
return NotificationListener<ScrollNotification>(
|
|
onNotification: (notification) {
|
|
// Listen for scroll notifications to load new data.
|
|
// Scroll controller does not work here due to nestedscrollview limitations.
|
|
final offset = notification.metrics.pixels;
|
|
if (offset > 128 && _savedViewsExpansionController.isExpanded) {
|
|
_savedViewsExpansionController.collapse();
|
|
}
|
|
|
|
final max = notification.metrics.maxScrollExtent;
|
|
final currentState = context.read<DocumentsCubit>().state;
|
|
if (max == 0 ||
|
|
currentState.isLoading ||
|
|
currentState.isLastPageLoaded) {
|
|
return false;
|
|
}
|
|
|
|
if (offset >= max * 0.7) {
|
|
context
|
|
.read<DocumentsCubit>()
|
|
.loadMore()
|
|
.onError<PaperlessApiException>(
|
|
(error, stackTrace) => showErrorMessage(
|
|
context,
|
|
error,
|
|
stackTrace,
|
|
),
|
|
);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
child: RefreshIndicator(
|
|
edgeOffset: kTextTabBarHeight + 2,
|
|
onRefresh: _reloadData,
|
|
notificationPredicate: (_) => connectivityState.isConnected,
|
|
child: CustomScrollView(
|
|
key: const PageStorageKey<String>("documents"),
|
|
slivers: <Widget>[
|
|
SliverOverlapInjector(handle: searchBarHandle),
|
|
SliverOverlapInjector(handle: savedViewsHandle),
|
|
SliverToBoxAdapter(
|
|
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
buildWhen: (previous, current) =>
|
|
previous.filter != current.filter,
|
|
builder: (context, state) {
|
|
final currentUser = context.watch<LocalUserAccount>();
|
|
if (!currentUser.paperlessUser.canViewSavedViews) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
return SavedViewsWidget(
|
|
controller: _savedViewsExpansionController,
|
|
onViewSelected: (view) {
|
|
final cubit = context.read<DocumentsCubit>();
|
|
if (state.filter.selectedView == view.id) {
|
|
_onResetFilter();
|
|
} else {
|
|
cubit.updateFilter(
|
|
filter: view.toDocumentFilter(),
|
|
);
|
|
}
|
|
},
|
|
onUpdateView: (view) async {
|
|
await context.read<SavedViewCubit>().update(view);
|
|
showSnackBar(
|
|
context, S.of(context)!.savedViewSuccessfullyUpdated);
|
|
},
|
|
onDeleteView: (view) async {
|
|
HapticFeedback.mediumImpact();
|
|
final shouldRemove = await showDialog(
|
|
context: context,
|
|
builder: (context) =>
|
|
ConfirmDeleteSavedViewDialog(view: view),
|
|
);
|
|
if (shouldRemove) {
|
|
final documentsCubit = context.read<DocumentsCubit>();
|
|
context.read<SavedViewCubit>().remove(view);
|
|
if (documentsCubit.state.filter.selectedView ==
|
|
view.id) {
|
|
documentsCubit.resetFilter();
|
|
}
|
|
}
|
|
},
|
|
filter: state.filter,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
builder: (context, state) {
|
|
if (state.hasLoaded && state.documents.isEmpty) {
|
|
return SliverToBoxAdapter(
|
|
child: DocumentsEmptyState(
|
|
state: state,
|
|
onReset: _onResetFilter,
|
|
),
|
|
);
|
|
}
|
|
final allowToggleFilter = state.selection.isEmpty;
|
|
return SliverAdaptiveDocumentsView(
|
|
viewType: state.viewType,
|
|
onTap: (document) {
|
|
DocumentDetailsRoute($extra: document).push(context);
|
|
},
|
|
onSelected:
|
|
context.read<DocumentsCubit>().toggleDocumentSelection,
|
|
hasInternetConnection: connectivityState.isConnected,
|
|
onTagSelected: allowToggleFilter ? _addTagToFilter : null,
|
|
onCorrespondentSelected:
|
|
allowToggleFilter ? _addCorrespondentToFilter : null,
|
|
onDocumentTypeSelected:
|
|
allowToggleFilter ? _addDocumentTypeToFilter : null,
|
|
onStoragePathSelected:
|
|
allowToggleFilter ? _addStoragePathToFilter : null,
|
|
documents: state.documents,
|
|
hasLoaded: state.hasLoaded,
|
|
isLabelClickable: true,
|
|
isLoading: state.isLoading,
|
|
selectedDocumentIds: state.selectedIds,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildViewActions() {
|
|
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
builder: (context, state) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(4),
|
|
color: Theme.of(context).colorScheme.background,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
SortDocumentsButton(
|
|
enabled: state.selection.isEmpty,
|
|
),
|
|
ViewTypeSelectionWidget(
|
|
viewType: state.viewType,
|
|
onChanged: context.read<DocumentsCubit>().setViewType,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _openDocumentFilter() async {
|
|
final draggableSheetController = DraggableScrollableController();
|
|
final filterIntent = await showModalBottomSheet<DocumentFilterIntent>(
|
|
useSafeArea: true,
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(16),
|
|
topRight: Radius.circular(16),
|
|
),
|
|
),
|
|
isScrollControlled: true,
|
|
builder: (_) => BlocProvider.value(
|
|
value: context.read<DocumentsCubit>(),
|
|
child: DraggableScrollableSheet(
|
|
controller: draggableSheetController,
|
|
expand: false,
|
|
snap: true,
|
|
snapSizes: const [0.9, 1],
|
|
initialChildSize: .9,
|
|
maxChildSize: 1,
|
|
builder: (context, controller) =>
|
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
builder: (context, state) {
|
|
return DocumentFilterPanel(
|
|
initialFilter: context.read<DocumentsCubit>().state.filter,
|
|
scrollController: controller,
|
|
draggableSheetController: draggableSheetController,
|
|
correspondents: state.correspondents,
|
|
documentTypes: state.documentTypes,
|
|
storagePaths: state.storagePaths,
|
|
tags: state.tags,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
if (filterIntent != null) {
|
|
try {
|
|
if (filterIntent.shouldReset) {
|
|
await _onResetFilter();
|
|
} else {
|
|
await context
|
|
.read<DocumentsCubit>()
|
|
.updateFilter(filter: filterIntent.filter!);
|
|
}
|
|
} on PaperlessApiException catch (error, stackTrace) {
|
|
showErrorMessage(context, error, stackTrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _addTagToFilter(int tagId) {
|
|
final cubit = context.read<DocumentsCubit>();
|
|
try {
|
|
switch (cubit.state.filter.tags) {
|
|
case IdsTagsQuery state:
|
|
if (state.include.contains(tagId)) {
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
tags: state.copyWith(
|
|
include: state.include
|
|
.whereNot((element) => element == tagId)
|
|
.toList(),
|
|
),
|
|
),
|
|
);
|
|
} else if (state.exclude.contains(tagId)) {
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
tags: state.copyWith(
|
|
exclude: state.exclude
|
|
.whereNot((element) => element == tagId)
|
|
.toList(),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
tags: state.copyWith(include: [...state.include, tagId]),
|
|
),
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(tags: IdsTagsQuery(include: [tagId])),
|
|
);
|
|
break;
|
|
}
|
|
} on PaperlessApiException catch (error, stackTrace) {
|
|
showErrorMessage(context, error, stackTrace);
|
|
}
|
|
}
|
|
|
|
void _addCorrespondentToFilter(int? correspondentId) {
|
|
if (correspondentId == null) return;
|
|
final cubit = context.read<DocumentsCubit>();
|
|
|
|
try {
|
|
switch (cubit.state.filter.correspondent) {
|
|
case SetIdQueryParameter(id: var id):
|
|
if (id == correspondentId) {
|
|
cubit.updateCurrentFilter(
|
|
(filter) =>
|
|
filter.copyWith(correspondent: const UnsetIdQueryParameter()),
|
|
);
|
|
} else {
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
correspondent: SetIdQueryParameter(id: correspondentId)),
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
cubit.updateCurrentFilter((filter) => filter.copyWith(
|
|
correspondent: SetIdQueryParameter(id: correspondentId),
|
|
));
|
|
break;
|
|
}
|
|
} on PaperlessApiException catch (error, stackTrace) {
|
|
showErrorMessage(context, error, stackTrace);
|
|
}
|
|
}
|
|
|
|
void _addDocumentTypeToFilter(int? documentTypeId) {
|
|
if (documentTypeId == null) return;
|
|
final cubit = context.read<DocumentsCubit>();
|
|
|
|
try {
|
|
switch (cubit.state.filter.documentType) {
|
|
case SetIdQueryParameter(id: var id):
|
|
if (id == documentTypeId) {
|
|
cubit.updateCurrentFilter(
|
|
(filter) =>
|
|
filter.copyWith(documentType: const UnsetIdQueryParameter()),
|
|
);
|
|
} else {
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
documentType: SetIdQueryParameter(id: documentTypeId)),
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
cubit.updateCurrentFilter(
|
|
(filter) => filter.copyWith(
|
|
documentType: SetIdQueryParameter(id: documentTypeId)),
|
|
);
|
|
break;
|
|
}
|
|
} on PaperlessApiException catch (error, stackTrace) {
|
|
showErrorMessage(context, error, stackTrace);
|
|
}
|
|
}
|
|
|
|
void _addStoragePathToFilter(int? pathId) {
|
|
if (pathId == null) return;
|
|
final cubit = context.read<DocumentsCubit>();
|
|
|
|
try {
|
|
switch (cubit.state.filter.storagePath) {
|
|
case SetIdQueryParameter(id: var id):
|
|
if (id == pathId) {
|
|
cubit.updateCurrentFilter(
|
|
(filter) =>
|
|
filter.copyWith(storagePath: const UnsetIdQueryParameter()),
|
|
);
|
|
} else {
|
|
cubit.updateCurrentFilter(
|
|
(filter) =>
|
|
filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)),
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
cubit.updateCurrentFilter(
|
|
(filter) =>
|
|
filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)),
|
|
);
|
|
break;
|
|
}
|
|
} on PaperlessApiException catch (error, stackTrace) {
|
|
showErrorMessage(context, error, stackTrace);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Resets the current filter and scrolls all the way to the top of the view.
|
|
/// If a saved view is currently selected and the filter has changed,
|
|
/// the user will be shown a dialog informing them about the changes.
|
|
/// The user can then decide whether to abort the reset or to continue and discard the changes.
|
|
Future<void> _onResetFilter() async {
|
|
final cubit = context.read<DocumentsCubit>();
|
|
final savedViewCubit = context.read<SavedViewCubit>();
|
|
|
|
void toTop() async {
|
|
await _nestedScrollViewKey.currentState?.outerController.animateTo(
|
|
0,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
|
|
final activeView = savedViewCubit.state.mapOrNull(
|
|
loaded: (state) {
|
|
if (cubit.state.filter.selectedView != null) {
|
|
return state.savedViews[cubit.state.filter.selectedView!];
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
final viewHasChanged = activeView != null &&
|
|
activeView.toDocumentFilter() != cubit.state.filter;
|
|
if (viewHasChanged) {
|
|
final discardChanges = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => const SavedViewChangedDialog(),
|
|
) ??
|
|
false;
|
|
if (discardChanges) {
|
|
cubit.resetFilter();
|
|
toTop();
|
|
}
|
|
} else {
|
|
cubit.resetFilter();
|
|
toTop();
|
|
}
|
|
}
|
|
}
|