Cleaned up code, implemented message queue to notify subscribers of document updates.

This commit is contained in:
Anton Stubenbord
2023-02-06 01:04:13 +01:00
parent 337c178be8
commit 4d7fab1839
111 changed files with 1412 additions and 1029 deletions

View File

@@ -160,8 +160,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
create: (context) => context.read<
LabelRepository<StoragePath, StoragePathRepositoryState>>(),
create: (context) => context.read<LabelRepository<StoragePath>>(),
child: AddStoragePathPage(initalValue: initialValue),
),
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
@@ -182,8 +181,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
create: (context) => context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
create: (context) => context.read<LabelRepository<Correspondent>>(),
child: AddCorrespondentPage(initialName: initialValue),
),
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
@@ -215,8 +213,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider(
create: (context) => context.read<
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
create: (context) => context.read<LabelRepository<DocumentType>>(),
child: AddDocumentTypePage(
initialName: currentInput,
),

View File

@@ -249,7 +249,7 @@ class _DocumentsPageState extends State<DocumentsPage>
Builder(
builder: (context) {
return RefreshIndicator(
edgeOffset: kToolbarHeight,
edgeOffset: kToolbarHeight + kTextTabBarHeight,
onRefresh: _onReloadDocuments,
notificationPredicate: (_) =>
connectivityState.isConnected,
@@ -263,13 +263,14 @@ class _DocumentsPageState extends State<DocumentsPage>
),
_buildViewActions(),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
!const ListEquality().equals(
previous.documents,
current.documents,
) ||
previous.selectedIds !=
current.selectedIds,
// Not required anymore since saved views are now handled separately
// buildWhen: (previous, current) =>
// !const ListEquality().equals(
// previous.documents,
// current.documents,
// ) ||
// previous.selectedIds !=
// current.selectedIds,
builder: (context, state) {
if (state.hasLoaded &&
state.documents.isEmpty) {
@@ -323,7 +324,7 @@ class _DocumentsPageState extends State<DocumentsPage>
Builder(
builder: (context) {
return RefreshIndicator(
edgeOffset: kToolbarHeight,
edgeOffset: kToolbarHeight + kTextTabBarHeight,
onRefresh: _onReloadSavedViews,
notificationPredicate: (_) =>
connectivityState.isConnected,
@@ -390,7 +391,7 @@ class _DocumentsPageState extends State<DocumentsPage>
try {
await context
.read<DocumentsCubit>()
.bulkRemove(documentsState.selection);
.bulkDelete(documentsState.selection);
showSnackBar(
context,
S.of(context).documentsPageBulkDeleteSuccessfulText,
@@ -467,20 +468,14 @@ class _DocumentsPageState extends State<DocumentsPage>
}
}
Future<void> _openDetails(DocumentModel document) async {
final updatedModel = await Navigator.pushNamed(
void _openDetails(DocumentModel document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
),
) as DocumentModel?;
// final updatedModel = await Navigator.of(context).push<DocumentModel?>(
// _buildDetailsPageRoute(document),
// );
if (updatedModel != document) {
context.read<DocumentsCubit>().reload();
}
);
}
void _addTagToFilter(int tagId) {

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_grid_loading_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
@@ -23,6 +23,7 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
final void Function(int? id)? onDocumentTypeSelected;
final void Function(int? id)? onStoragePathSelected;
bool get showLoadingPlaceholder => (!hasLoaded && isLoading);
const AdaptiveDocumentsView({
super.key,
this.selectedDocumentIds = const [],
@@ -56,6 +57,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
super.onTap,
super.selectedDocumentIds,
super.viewType,
super.enableHeroAnimation,
required super.isLoading,
required super.hasLoaded,
});
@@ -71,8 +73,8 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
}
Widget _buildListView() {
if (!hasLoaded && isLoading) {
return const DocumentsListLoadingWidget();
if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget.sliver();
}
return SliverList(
delegate: SliverChildBuilderDelegate(
@@ -91,6 +93,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
),
);
},
@@ -99,8 +102,8 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
}
Widget _buildGridView() {
if (!hasLoaded && isLoading) {
return const DocumentsListLoadingWidget();
if (showLoadingPlaceholder) {
return DocumentGridLoadingWidget.sliver();
}
return SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
@@ -162,10 +165,8 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
}
Widget _buildListView() {
if (!hasLoaded && isLoading) {
return const CustomScrollView(slivers: [
DocumentsListLoadingWidget(),
]);
if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget();
}
return ListView.builder(
@@ -194,12 +195,8 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
}
Widget _buildGridView() {
if (!hasLoaded && isLoading) {
return const CustomScrollView(
slivers: [
DocumentsListLoadingWidget(),
],
); //TODO: Build grid skeleton
if (showLoadingPlaceholder) {
return DocumentGridLoadingWidget();
}
return GridView.builder(
controller: scrollController,

View File

@@ -0,0 +1,102 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.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/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.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:shimmer/shimmer.dart';
class DocumentGridLoadingWidget extends StatelessWidget
with DocumentItemPlaceholder {
final bool _isSliver;
@override
final Random random = Random(1257195195);
DocumentGridLoadingWidget({super.key}) : _isSliver = false;
DocumentGridLoadingWidget.sliver({super.key}) : _isSliver = true;
@override
Widget build(BuildContext context) {
const delegate = SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
childAspectRatio: 1 / 2,
);
if (_isSliver) {
return SliverGrid.builder(
gridDelegate: delegate,
itemBuilder: (context, index) => _buildPlaceholderGridItem(context),
);
}
return GridView.builder(
gridDelegate: delegate,
itemBuilder: (context, index) => _buildPlaceholderGridItem(context),
);
}
Widget _buildPlaceholderGridItem(BuildContext context) {
final values = nextValues;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 1.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(
child: AspectRatio(
aspectRatio: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Container(
color: Colors.white,
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ShimmerPlaceholder(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextPlaceholder(
length: values.correspondentLength,
fontSize: 16,
).padded(1),
TextPlaceholder(
length: values.titleLength,
fontSize: 16,
),
if (values.tagCount > 0) ...[
const Spacer(),
TagsPlaceholder(
count: values.tagCount,
dense: true,
),
],
const Spacer(),
TextPlaceholder(
length: 100,
fontSize:
Theme.of(context).textTheme.bodySmall!.fontSize!,
),
],
),
),
),
),
],
),
),
);
}
}

View File

@@ -1,42 +1,42 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:shimmer/shimmer.dart';
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.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';
class DocumentsListLoadingWidget extends StatelessWidget {
static const _tags = [" ", " ", " "];
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
static const _correspondentLengths = <double>[200.0, 300.0, 150.0];
static const _fontSize = 16.0;
class DocumentsListLoadingWidget extends StatelessWidget
with DocumentItemPlaceholder {
final bool _isSliver;
DocumentsListLoadingWidget({super.key}) : _isSliver = false;
const DocumentsListLoadingWidget({super.key
});
DocumentsListLoadingWidget.sliver({super.key}) : _isSliver = true;
@override
final Random random = Random(1209571050);
@override
Widget build(BuildContext context) {
final _random = Random();
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return _buildFakeListItem(context, _random);
},
),
);
if (_isSliver) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _buildFakeListItem(context),
),
);
} else {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildFakeListItem(context),
);
}
}
Widget _buildFakeListItem(BuildContext context, Random random) {
final tagCount = random.nextInt(_tags.length + 1);
final correspondentLength =
_correspondentLengths[random.nextInt(_correspondentLengths.length - 1)];
final titleLength = _titleLengths[random.nextInt(_titleLengths.length - 1)];
return Shimmer.fromColors(
baseColor: Theme.of(context).brightness == Brightness.light
? Colors.grey[300]!
: Colors.grey[900]!,
highlightColor: Theme.of(context).brightness == Brightness.light
? Colors.grey[100]!
: Colors.grey[600]!,
Widget _buildFakeListItem(BuildContext context) {
const fontSize = 14.0;
final values = nextValues;
return ShimmerPlaceholder(
child: ListTile(
contentPadding: const EdgeInsets.all(8),
dense: true,
@@ -45,15 +45,17 @@ class DocumentsListLoadingWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
child: Container(
color: Colors.white,
height: 50,
height: double.infinity,
width: 35,
),
),
title: Container(
padding: const EdgeInsets.symmetric(vertical: 2.0),
width: correspondentLength,
height: _fontSize,
color: Colors.white,
title: Row(
children: [
TextPlaceholder(
length: values.correspondentLength,
fontSize: fontSize,
),
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
@@ -61,21 +63,16 @@ class DocumentsListLoadingWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 2.0),
height: _fontSize,
width: titleLength,
color: Colors.white,
TextPlaceholder(
length: values.titleLength,
fontSize: fontSize,
),
if (values.tagCount > 0)
TagsPlaceholder(count: values.tagCount, dense: true),
TextPlaceholder(
length: 100,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize!,
),
Wrap(
spacing: 2.0,
children: List.generate(
tagCount,
(index) => InputChip(
label: Text(_tags[random.nextInt(_tags.length)]),
),
),
).paddedOnly(top: 4),
],
),
),

View File

@@ -56,7 +56,7 @@ class DocumentListItem extends DocumentItem {
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: document.tags.isEmpty ? 2 : 1,
maxLines: 1,
),
AbsorbPointer(
absorbing: isSelectionActive,

View File

@@ -0,0 +1,30 @@
import 'dart:math';
mixin DocumentItemPlaceholder {
static const _tags = [" ", " ", " "];
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
static const _correspondentLengths = <double>[120.0, 80.0, 40.0];
Random get random;
RandomDocumentItemPlaceholderValues get nextValues {
return RandomDocumentItemPlaceholderValues(
tagCount: random.nextInt(_tags.length + 1),
correspondentLength: _correspondentLengths[
random.nextInt(_correspondentLengths.length - 1)],
titleLength: _titleLengths[random.nextInt(_titleLengths.length - 1)],
);
}
}
class RandomDocumentItemPlaceholderValues {
final int tagCount;
final double correspondentLength;
final double titleLength;
RandomDocumentItemPlaceholderValues({
required this.tagCount,
required this.correspondentLength,
required this.titleLength,
});
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class TagsPlaceholder extends StatelessWidget {
static const _lengths = [24, 36, 16, 48];
final int count;
final bool dense;
const TagsPlaceholder({
super.key,
required this.count,
required this.dense,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 32,
child: ListView.separated(
itemCount: count,
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(),
),
),
separatorBuilder: (context, _) => const SizedBox(width: 4),
),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
class TextPlaceholder extends StatelessWidget {
final double length;
final double fontSize;
const TextPlaceholder({
super.key,
required this.length,
required this.fontSize,
});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
width: length,
height: fontSize,
);
}
}

View File

@@ -44,16 +44,12 @@ class SortDocumentsButton extends StatelessWidget {
providers: [
BlocProvider(
create: (context) => LabelCubit<DocumentType>(
context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
context.read<LabelRepository<DocumentType>>(),
),
),
BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
context.read<LabelRepository<Correspondent>>(),
),
),
],