diff --git a/lib/features/document_details/view/pages/document_details_page.dart b/lib/features/document_details/view/pages/document_details_page.dart index a53d29a..59539ed 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -1,7 +1,4 @@ -import 'dart:io'; - import 'package:badges/badges.dart' as b; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:open_filex/open_filex.dart'; @@ -22,12 +19,8 @@ import 'package:paperless_mobile/features/documents/view/widgets/document_previe import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart'; import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; - import 'package:paperless_mobile/helpers/message_helpers.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:share_plus/share_plus.dart'; -//TODO: Refactor this into several widgets class DocumentDetailsPage extends StatefulWidget { final bool allowEdit; final bool isLabelClickable; diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index d5cd067..ca8bed7 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -287,39 +287,39 @@ class _DocumentsPageState extends State ConnectivityState connectivityState, BuildContext context, ) { - return RefreshIndicator( - edgeOffset: kTextTabBarHeight, - onRefresh: _onReloadDocuments, - notificationPredicate: (_) => connectivityState.isConnected, - child: NotificationListener( - onNotification: (notification) { - // Listen for scroll notifications to load new data. - // Scroll controller does not work here due to nestedscrollview limitations. + return NotificationListener( + onNotification: (notification) { + // Listen for scroll notifications to load new data. + // Scroll controller does not work here due to nestedscrollview limitations. - final currState = context.read().state; - final max = notification.metrics.maxScrollExtent; - if (max == 0 || - _currentTab != 0 || - currState.isLoading || - currState.isLastPageLoaded) { - return true; - } + final currState = context.read().state; + final max = notification.metrics.maxScrollExtent; + if (max == 0 || + _currentTab != 0 || + currState.isLoading || + currState.isLastPageLoaded) { + return true; + } - final offset = notification.metrics.pixels; - if (offset >= max * 0.7) { - context - .read() - .loadMore() - .onError( - (error, stackTrace) => showErrorMessage( - context, - error, - stackTrace, - ), - ); - } - return false; - }, + final offset = notification.metrics.pixels; + if (offset >= max * 0.7) { + context + .read() + .loadMore() + .onError( + (error, stackTrace) => showErrorMessage( + context, + error, + stackTrace, + ), + ); + } + return false; + }, + child: RefreshIndicator( + edgeOffset: kTextTabBarHeight, + onRefresh: _onReloadDocuments, + notificationPredicate: (_) => connectivityState.isConnected, child: CustomScrollView( key: const PageStorageKey("documents"), slivers: [ diff --git a/lib/features/documents/view/widgets/placeholder/tags_placeholder.dart b/lib/features/documents/view/widgets/placeholder/tags_placeholder.dart index 85c5caf..22528e1 100644 --- a/lib/features/documents/view/widgets/placeholder/tags_placeholder.dart +++ b/lib/features/documents/view/widgets/placeholder/tags_placeholder.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:paperless_mobile/extensions/flutter_extensions.dart'; class TagsPlaceholder extends StatelessWidget { - static const _lengths = [24, 36, 16, 48]; + static const _lengths = [90, 70, 130]; final int count; final bool dense; const TagsPlaceholder({ @@ -14,24 +15,23 @@ class TagsPlaceholder extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 32, - child: ListView.separated( - padding: EdgeInsets.zero, - itemCount: count, + child: SingleChildScrollView( scrollDirection: Axis.horizontal, - itemBuilder: (context, index) => FilterChip( - labelPadding: - dense ? const EdgeInsets.symmetric(horizontal: 2) : null, - padding: dense ? const EdgeInsets.all(4) : null, - visualDensity: const VisualDensity(vertical: -2), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: BorderSide.none, - onSelected: (_) {}, - selected: false, - label: Text( - List.filled(_lengths[index], " ").join(), - ), + physics: const NeverScrollableScrollPhysics(), + child: Row( + children: List.generate(count, (index) => index) + .map( + (index) => Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + width: _lengths[index % _lengths.length], + height: 32, + ).paddedOnly(right: 4), + ) + .toList(), ), - separatorBuilder: (context, _) => const SizedBox(width: 4), ), ); } diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index 2b008a0..e9ce8d9 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -128,6 +128,34 @@ class InboxCubit extends HydratedCubit ); } + /// + /// Fetches inbox tag ids and loads the inbox items (documents). + /// + Future reloadInbox() async { + emit(state.copyWith(hasLoaded: false, isLoading: true)); + final inboxTags = await _tagsRepository.findAll().then( + (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), + ); + + if (inboxTags.isEmpty) { + // no inbox tags = no inbox items. + return emit( + state.copyWith( + hasLoaded: true, + value: [], + inboxTags: [], + ), + ); + } + emit(state.copyWith(inboxTags: inboxTags)); + updateFilter( + filter: DocumentFilter( + sortField: SortField.added, + tags: IdsTagsQuery.fromIds(inboxTags), + ), + ); + } + /// /// Updates the document with all inbox tags removed and removes the document /// from the inbox. diff --git a/lib/features/inbox/view/pages/inbox_page.dart b/lib/features/inbox/view/pages/inbox_page.dart index 7026e2b..6d33d6c 100644 --- a/lib/features/inbox/view/pages/inbox_page.dart +++ b/lib/features/inbox/view/pages/inbox_page.dart @@ -3,20 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; -import 'package:paperless_mobile/features/document_search/view/document_search_page.dart'; import 'package:paperless_mobile/core/widgets/hint_card.dart'; import 'package:paperless_mobile/extensions/dart_extensions.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/view/widgets/placeholder/documents_list_loading_widget.dart'; import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart'; import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart'; import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart'; +import 'package:paperless_mobile/features/inbox/view/widgets/inbox_list_loading_widget.dart'; import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart'; -import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; - import 'package:paperless_mobile/helpers/message_helpers.dart'; class InboxPage extends StatefulWidget { @@ -38,7 +36,7 @@ class _InboxPageState extends State @override void initState() { super.initState(); - context.read().loadInbox(); + context.read().reloadInbox(); } @override @@ -76,7 +74,7 @@ class _InboxPageState extends State body: Builder( builder: (context) { if (!state.hasLoaded) { - return const DocumentsListLoadingWidget(); //TODO: Implement InboxLoadingWidget... + return const InboxListLoadingWidget(); } else if (state.documents.isEmpty) { return Center( child: InboxEmptyWidget( @@ -86,7 +84,6 @@ class _InboxPageState extends State ); } else { return RefreshIndicator( - edgeOffset: kToolbarHeight, onRefresh: context.read().reload, child: CustomScrollView( slivers: [ diff --git a/lib/features/inbox/view/widgets/inbox_item.dart b/lib/features/inbox/view/widgets/inbox_item.dart index 8aecb92..645960c 100644 --- a/lib/features/inbox/view/widgets/inbox_item.dart +++ b/lib/features/inbox/view/widgets/inbox_item.dart @@ -13,7 +13,7 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/routes/document_details_route.dart'; class InboxItem extends StatefulWidget { - static const _a4AspectRatio = 1 / 1.4142; + static const a4AspectRatio = 1 / 1.4142; final DocumentModel document; const InboxItem({ @@ -53,7 +53,7 @@ class _InboxItemState extends State { child: Row( children: [ AspectRatio( - aspectRatio: InboxItem._a4AspectRatio, + aspectRatio: InboxItem.a4AspectRatio, child: DocumentPreview( document: widget.document, fit: BoxFit.cover, diff --git a/lib/features/inbox/view/widgets/inbox_list_loading_widget.dart b/lib/features/inbox/view/widgets/inbox_list_loading_widget.dart new file mode 100644 index 0000000..ce9280c --- /dev/null +++ b/lib/features/inbox/view/widgets/inbox_list_loading_widget.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart'; +import 'package:paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart'; +import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart'; +import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart'; + +class InboxListLoadingWidget extends StatelessWidget { + const InboxListLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.separated( + itemCount: 20, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildInboxItem().padded(), + separatorBuilder: (context, index) => const SizedBox(height: 16), + ).paddedOnly(top: 8); + } + + Widget _buildInboxItem() { + return ShimmerPlaceholder( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const TextPlaceholder(length: 150, fontSize: 12), + const SizedBox( + height: 16, + ), + SizedBox( + height: 200, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 150, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 120, + width: 90, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: const ColoredBox( + color: Colors.white, + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Spacer(), + TextPlaceholder(length: 200, fontSize: 14), + Spacer(), + TextPlaceholder(length: 120, fontSize: 14), + SizedBox(height: 8), + TextPlaceholder(length: 170, fontSize: 14), + Spacer(), + TagsPlaceholder(count: 3, dense: true), + Spacer(), + ], + ), + ), + ], + ), + ), + SizedBox( + height: 50, + child: IntrinsicHeight( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + child: Row( + children: [ + const SizedBox( + width: 50, + height: 40, + child: ColoredBox( + color: Colors.white, + ), + ).padded(), + const VerticalDivider( + indent: 12, + endIndent: 12, + ), + SizedBox( + height: 40, + child: Row( + children: [ + Container( + width: 150, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.white, + ), + ), + const SizedBox(width: 4), + Container( + width: 200, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.white, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart index 17a6de6..ccab24b 100644 --- a/lib/features/labels/view/pages/labels_page.dart +++ b/lib/features/labels/view/pages/labels_page.dart @@ -6,7 +6,6 @@ import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_he import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; -import 'package:paperless_mobile/features/document_search/view/document_search_page.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart'; @@ -18,7 +17,6 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart'; -import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; class LabelsPage extends StatefulWidget { @@ -140,7 +138,7 @@ class _LabelsPageState extends State return true; }, child: RefreshIndicator( - edgeOffset: kToolbarHeight + kTextTabBarHeight, + edgeOffset: kTextTabBarHeight, notificationPredicate: (notification) => connectedState.isConnected, onRefresh: () => [ diff --git a/lib/main.dart b/lib/main.dart index 8ac517e..f53d7e1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -276,9 +276,9 @@ class _AuthenticationWrapperState extends State { super.initState(); // Temporary Fix: Can be removed if the flutter engine implements the fix itself // Activate the highest supported refresh rate on the device - if (Platform.isAndroid) { - _setOptimalDisplayMode(); - } + // if (Platform.isAndroid) { + // _setOptimalDisplayMode(); + // } initializeDateFormatting(); // For sharing files coming from outside the app while the app is still opened ReceiveSharingIntent.getMediaStream()