Updated onboarding, reformatted files, improved referenced documents view, updated error handling

This commit is contained in:
Anton Stubenbord
2022-11-03 22:15:36 +01:00
parent 2f2312d5f3
commit 40133b6e0e
117 changed files with 1788 additions and 1021 deletions

View File

@@ -26,7 +26,8 @@ class DeleteDocumentConfirmationDialog extends StatelessWidget {
),
),
const SizedBox(height: 16),
Text(S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
Text(
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
],
),
actions: [
@@ -36,7 +37,8 @@ class DeleteDocumentConfirmationDialog extends StatelessWidget {
),
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error),
foregroundColor:
MaterialStateProperty.all(Theme.of(context).colorScheme.error),
),
onPressed: () {
Navigator.pop(context, true);

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/widgets/empty_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
@@ -7,6 +9,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class DocumentsEmptyState extends StatelessWidget {
final DocumentsState state;

View File

@@ -51,8 +51,10 @@ class DocumentGridItem extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CorrespondentWidget(correspondentId: document.correspondent),
DocumentTypeWidget(documentTypeId: document.documentType),
CorrespondentWidget(
correspondentId: document.correspondent),
DocumentTypeWidget(
documentTypeId: document.documentType),
Text(
document.title,
maxLines: document.tags.isEmpty ? 3 : 2,
@@ -64,7 +66,8 @@ class DocumentGridItem extends StatelessWidget {
tagIds: document.tags,
isMultiLine: false,
),
Text(DateFormat.yMMMd(Intl.getCurrentLocale()).format(document.created)),
Text(DateFormat.yMMMd(Intl.getCurrentLocale())
.format(document.created)),
],
),
),

View File

@@ -7,12 +7,13 @@ import 'package:paperless_mobile/features/documents/view/widgets/list/document_l
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class DocumentListView extends StatelessWidget {
final void Function(DocumentModel model) onTap;
final void Function(DocumentModel) onTap;
final void Function(DocumentModel) onSelected;
final PagingController<int, DocumentModel> pagingController;
final DocumentsState state;
final bool hasInternetConnection;
final bool isLabelClickable;
const DocumentListView({
super.key,
required this.onTap,
@@ -20,24 +21,28 @@ class DocumentListView extends StatelessWidget {
required this.state,
required this.onSelected,
required this.hasInternetConnection,
this.isLabelClickable = true,
});
@override
Widget build(BuildContext context) {
return PagedSliverList<int, DocumentModel>(
pagingController: pagingController,
builderDelegate: PagedChildBuilderDelegate(
animateTransitions: true,
itemBuilder: (context, item, index) {
itemBuilder: (context, document, index) {
return DocumentListItem(
document: item,
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: state.selection.contains(item),
isSelected: state.selection.contains(document),
onSelected: onSelected,
isAtLeastOneSelected: state.selection.isNotEmpty,
);
},
noItemsFoundIndicatorBuilder: (context) =>
hasInternetConnection ? const DocumentsListLoadingWidget() : const OfflineWidget(),
noItemsFoundIndicatorBuilder: (context) => hasInternetConnection
? const DocumentsListLoadingWidget()
: const OfflineWidget(),
),
);
}

View File

@@ -5,12 +5,13 @@ import 'package:paperless_mobile/features/labels/correspondent/view/widgets/corr
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
class DocumentListItem extends StatelessWidget {
static const a4AspectRatio = 1 / 1.4142;
static const _a4AspectRatio = 1 / 1.4142;
final DocumentModel document;
final bool isSelected;
final void Function(DocumentModel) onTap;
final void Function(DocumentModel)? onSelected;
final bool isSelected;
final bool isAtLeastOneSelected;
final bool isLabelClickable;
const DocumentListItem({
Key? key,
@@ -19,6 +20,7 @@ class DocumentListItem extends StatelessWidget {
this.onSelected,
required this.isSelected,
required this.isAtLeastOneSelected,
this.isLabelClickable = true,
}) : super(key: key);
@override
@@ -39,6 +41,7 @@ class DocumentListItem extends StatelessWidget {
AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondentId: document.correspondent,
afterSelected: () {},
),
@@ -57,6 +60,7 @@ class DocumentListItem extends StatelessWidget {
child: AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: TagsWidget(
isClickable: isLabelClickable,
tagIds: document.tags,
isMultiLine: false,
),
@@ -64,7 +68,7 @@ class DocumentListItem extends StatelessWidget {
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: a4AspectRatio,
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
id: document.id,

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
@@ -24,6 +26,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:intl/intl.dart';
import 'package:paperless_mobile/util.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
enum DateRangeSelection { before, after }
@@ -108,7 +111,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
alignment: Alignment.topRight,
child: TextButton.icon(
icon: const Icon(Icons.refresh),
label: Text(S.of(context).documentsFilterPageResetFilterLabel),
label: Text(
S.of(context).documentsFilterPageResetFilterLabel),
onPressed: () => _resetFilter(context),
),
),
@@ -126,7 +130,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
),
TextButton(
onPressed: _onApplyFilter,
child: Text(S.of(context).documentsFilterPageApplyFilterLabel),
child: Text(
S.of(context).documentsFilterPageApplyFilterLabel),
),
],
).padded(),
@@ -225,15 +230,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
}
Widget _buildQueryFormField(DocumentsState state) {
final queryType = _formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ??
QueryType.titleAndContent;
final queryType =
_formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ??
QueryType.titleAndContent;
late String label;
switch (queryType) {
case QueryType.title:
label = S.of(context).documentsFilterPageQueryOptionsTitleLabel;
break;
case QueryType.titleAndContent:
label = S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel;
label =
S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel;
break;
case QueryType.extended:
label = S.of(context).documentsFilterPageQueryOptionsExtendedLabel;
@@ -255,7 +262,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
).padded();
}
Widget _buildDateRangePickerHelper(DocumentsState state, String formFieldKey) {
Widget _buildDateRangePickerHelper(
DocumentsState state, String formFieldKey) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
@@ -279,10 +287,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
),
onPressed: () {
final now = DateTime.now();
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -1);
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -1);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(firstDayOfLastMonth.year, firstDayOfLastMonth.month, now.day),
start: DateTime(firstDayOfLastMonth.year,
firstDayOfLastMonth.month, now.day),
end: DateTime.now(),
),
);
@@ -294,7 +304,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
),
onPressed: () {
final now = DateTime.now();
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -3);
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -3);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(
@@ -313,7 +324,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
),
onPressed: () {
final now = DateTime.now();
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -12);
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -12);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(
@@ -345,7 +357,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
data: Theme.of(context).copyWith(
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBarTheme: Theme.of(context).appBarTheme.copyWith(
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
iconTheme:
IconThemeData(color: Theme.of(context).primaryColor),
),
colorScheme: Theme.of(context).colorScheme.copyWith(
onPrimary: Theme.of(context).primaryColor,
@@ -355,8 +368,10 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
child: child!,
),
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
fieldStartLabelText:
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
fieldEndLabelText:
S.of(context).documentsFilterPageDateRangeFieldEndLabel,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now(),
name: fkCreatedAt,
@@ -365,7 +380,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
labelText: S.of(context).documentCreatedPropertyLabel,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkCreatedAt]?.didChange(null),
onPressed: () =>
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null),
),
),
),
@@ -388,7 +404,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
data: Theme.of(context).copyWith(
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBarTheme: Theme.of(context).appBarTheme.copyWith(
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
iconTheme:
IconThemeData(color: Theme.of(context).primaryColor),
),
colorScheme: Theme.of(context).colorScheme.copyWith(
onPrimary: Theme.of(context).primaryColor,
@@ -398,8 +415,10 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
child: child!,
),
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
fieldStartLabelText:
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
fieldEndLabelText:
S.of(context).documentsFilterPageDateRangeFieldEndLabel,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now(),
name: fkAddedAt,
@@ -408,7 +427,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
labelText: S.of(context).documentAddedPropertyLabel,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkAddedAt]?.didChange(null),
onPressed: () =>
_formKey.currentState?.fields[fkAddedAt]?.didChange(null),
),
),
),
@@ -444,16 +464,16 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
separatorBuilder: (context, index) => const SizedBox(
width: 8.0,
),
itemBuilder: (context, index) =>
_buildActionChip(_sortFields[index], state.filter.sortField, context),
itemBuilder: (context, index) => _buildActionChip(
_sortFields[index], state.filter.sortField, context),
),
),
],
).padded();
}
Widget _buildActionChip(
SortField sortField, SortField? currentlySelectedOrder, BuildContext context) {
Widget _buildActionChip(SortField sortField,
SortField? currentlySelectedOrder, BuildContext context) {
String text;
switch (sortField) {
case SortField.archiveSerialNumber:
@@ -488,8 +508,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
color: Colors.green,
)
: null,
onPressed: () =>
docBloc.updateFilter(filter: docBloc.state.filter.copyWith(sortField: sortField)),
onPressed: () => docBloc.updateFilter(
filter: docBloc.state.filter.copyWith(sortField: sortField)),
);
}
@@ -510,7 +530,9 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
);
BlocProvider.of<DocumentsCubit>(context).updateFilter(filter: newFilter).then((value) {
BlocProvider.of<DocumentsCubit>(context)
.updateFilter(filter: newFilter)
.then((value) {
BlocProvider.of<SavedViewCubit>(context).resetSelection();
FocusScope.of(context).unfocus();
widget.panelController.close();

View File

@@ -20,19 +20,23 @@ class QueryTypeFormField extends StatelessWidget {
itemBuilder: (context) => [
PopupMenuItem(
child: ListTile(
title: Text(S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel),
title: Text(S
.of(context)
.documentsFilterPageQueryOptionsTitleAndContentLabel),
),
value: QueryType.titleAndContent,
),
PopupMenuItem(
child: ListTile(
title: Text(S.of(context).documentsFilterPageQueryOptionsTitleLabel),
title:
Text(S.of(context).documentsFilterPageQueryOptionsTitleLabel),
),
value: QueryType.title,
),
PopupMenuItem(
child: ListTile(
title: Text(S.of(context).documentsFilterPageQueryOptionsExtendedLabel),
title: Text(
S.of(context).documentsFilterPageQueryOptionsExtendedLabel),
),
value: QueryType.extended,
),

View File

@@ -76,7 +76,8 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
SavedView.fromDocumentFilter(
widget.currentFilter,
name: _formKey.currentState?.value[fkName] as String,
showOnDashboard: _formKey.currentState?.value[fkShowOnDashboard] as bool,
showOnDashboard:
_formKey.currentState?.value[fkShowOnDashboard] as bool,
showInSidebar: _formKey.currentState?.value[fkShowInSidebar] as bool,
),
);

View File

@@ -6,7 +6,8 @@ import 'package:paperless_mobile/generated/l10n.dart';
class BulkDeleteConfirmationDialog extends StatelessWidget {
static const _bulletPoint = "\u2022";
final DocumentsState state;
const BulkDeleteConfirmationDialog({Key? key, required this.state}) : super(key: key);
const BulkDeleteConfirmationDialog({Key? key, required this.state})
: super(key: key);
@override
Widget build(BuildContext context) {
@@ -19,8 +20,12 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
Text(
//TODO: use plurals, didn't use because of crash... investigate later.
state.selection.length == 1
? S.of(context).documentsPageSelectionBulkDeleteDialogWarningTextOne
: S.of(context).documentsPageSelectionBulkDeleteDialogWarningTextMany,
? S
.of(context)
.documentsPageSelectionBulkDeleteDialogWarningTextOne
: S
.of(context)
.documentsPageSelectionBulkDeleteDialogWarningTextMany,
),
const SizedBox(height: 16),
ConstrainedBox(
@@ -31,7 +36,8 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
),
),
const SizedBox(height: 16),
Text(S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
Text(
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
],
),
actions: [
@@ -41,7 +47,8 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
),
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error),
foregroundColor:
MaterialStateProperty.all(Theme.of(context).colorScheme.error),
),
onPressed: () {
Navigator.pop(context, true);

View File

@@ -36,10 +36,11 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
expandedHeight: kToolbarHeight,
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => BlocProvider.of<DocumentsCubit>(context).resetSelection(),
onPressed: () =>
BlocProvider.of<DocumentsCubit>(context).resetSelection(),
),
title:
Text('${documentsState.selection.length} ${S.of(context).documentsSelectedText}'),
title: Text(
'${documentsState.selection.length} ${S.of(context).documentsSelectedText}'),
actions: [
IconButton(
icon: const Icon(Icons.delete),
@@ -79,9 +80,8 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
if (shouldDelete ?? false) {
BlocProvider.of<DocumentsCubit>(context)
.bulkRemoveDocuments(documentsState.selection)
.then((_) => showSnackBar(context, S.of(context).documentsPageBulkDeleteSuccessfulText))
.onError<ErrorMessage>(
(error, _) => showSnackBar(context, translateError(context, error.code)));
.then((_) => showSnackBar(
context, S.of(context).documentsPageBulkDeleteSuccessfulText));
}
}

View File

@@ -44,7 +44,8 @@ class SavedViewSelectionWidget extends StatelessWidget {
child: FilterChip(
label: Text(state.value.values.toList()[index].name),
selected: view.id == state.selectedSavedViewId,
onSelected: (isSelected) => _onSelected(isSelected, context, view),
onSelected: (isSelected) =>
_onSelected(isSelected, context, view),
),
);
},
@@ -76,21 +77,19 @@ class SavedViewSelectionWidget extends StatelessWidget {
void _onCreatePressed(BuildContext context) async {
final newView = await Navigator.of(context).push<SavedView?>(
MaterialPageRoute(
builder: (context) => AddSavedViewPage(currentFilter: getIt<DocumentsCubit>().state.filter),
builder: (context) => AddSavedViewPage(
currentFilter: getIt<DocumentsCubit>().state.filter),
),
);
if (newView != null) {
try {
BlocProvider.of<SavedViewCubit>(context).add(newView);
} on ErrorMessage catch (error) {
showError(context, error);
}
BlocProvider.of<SavedViewCubit>(context).add(newView);
}
}
void _onSelected(bool isSelected, BuildContext context, SavedView view) {
if (isSelected) {
BlocProvider.of<DocumentsCubit>(context).updateFilter(filter: view.toDocumentFilter());
BlocProvider.of<DocumentsCubit>(context)
.updateFilter(filter: view.toDocumentFilter());
BlocProvider.of<SavedViewCubit>(context).selectView(view);
} else {
BlocProvider.of<DocumentsCubit>(context).updateFilter();
@@ -106,11 +105,7 @@ class SavedViewSelectionWidget extends StatelessWidget {
) ??
false;
if (delete) {
try {
BlocProvider.of<SavedViewCubit>(context).remove(view);
} on ErrorMessage catch (error) {
showError(context, error);
}
BlocProvider.of<SavedViewCubit>(context).remove(view);
}
}
}

View File

@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_order.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:paperless_mobile/util.dart';
class SortDocumentsButton extends StatefulWidget {
const SortDocumentsButton({
@@ -30,16 +33,20 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
),
);
} else {
final bool isAscending = state.filter.sortOrder == SortOrder.ascending;
final bool isAscending =
state.filter.sortOrder == SortOrder.ascending;
child = IconButton(
icon: FaIcon(
isAscending ? FontAwesomeIcons.arrowDownAZ : FontAwesomeIcons.arrowUpZA,
isAscending
? FontAwesomeIcons.arrowDownAZ
: FontAwesomeIcons.arrowUpZA,
),
onPressed: () async {
setState(() => _isLoading = true);
BlocProvider.of<DocumentsCubit>(context)
.updateFilter(
filter: state.filter.copyWith(sortOrder: state.filter.sortOrder.toggle()))
filter: state.filter
.copyWith(sortOrder: state.filter.sortOrder.toggle()))
.whenComplete(() => setState(() => _isLoading = false));
},
);