feat: Improve inbox loading animation, fix refresh indicator positions

This commit is contained in:
Anton Stubenbord
2023-02-24 13:42:58 +01:00
parent 7464266ac5
commit 38aee2b052
9 changed files with 211 additions and 70 deletions

View File

@@ -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;

View File

@@ -287,11 +287,7 @@ class _DocumentsPageState extends State<DocumentsPage>
ConnectivityState connectivityState,
BuildContext context,
) {
return RefreshIndicator(
edgeOffset: kTextTabBarHeight,
onRefresh: _onReloadDocuments,
notificationPredicate: (_) => connectivityState.isConnected,
child: NotificationListener<ScrollNotification>(
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
// Listen for scroll notifications to load new data.
// Scroll controller does not work here due to nestedscrollview limitations.
@@ -320,6 +316,10 @@ class _DocumentsPageState extends State<DocumentsPage>
}
return false;
},
child: RefreshIndicator(
edgeOffset: kTextTabBarHeight,
onRefresh: _onReloadDocuments,
notificationPredicate: (_) => connectivityState.isConnected,
child: CustomScrollView(
key: const PageStorageKey<String>("documents"),
slivers: <Widget>[

View File

@@ -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 = <double>[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),
),
);
}

View File

@@ -128,6 +128,34 @@ class InboxCubit extends HydratedCubit<InboxState>
);
}
///
/// Fetches inbox tag ids and loads the inbox items (documents).
///
Future<void> 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.

View File

@@ -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<InboxPage>
@override
void initState() {
super.initState();
context.read<InboxCubit>().loadInbox();
context.read<InboxCubit>().reloadInbox();
}
@override
@@ -76,7 +74,7 @@ class _InboxPageState extends State<InboxPage>
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<InboxPage>
);
} else {
return RefreshIndicator(
edgeOffset: kToolbarHeight,
onRefresh: context.read<InboxCubit>().reload,
child: CustomScrollView(
slivers: [

View File

@@ -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<InboxItem> {
child: Row(
children: [
AspectRatio(
aspectRatio: InboxItem._a4AspectRatio,
aspectRatio: InboxItem.a4AspectRatio,
child: DocumentPreview(
document: widget.document,
fit: BoxFit.cover,

View File

@@ -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,
),
),
],
),
),
],
),
),
),
),
],
),
),
],
),
);
}
}

View File

@@ -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<LabelsPage>
return true;
},
child: RefreshIndicator(
edgeOffset: kToolbarHeight + kTextTabBarHeight,
edgeOffset: kTextTabBarHeight,
notificationPredicate: (notification) =>
connectedState.isConnected,
onRefresh: () => [

View File

@@ -276,9 +276,9 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
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()