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

@@ -12,7 +12,8 @@ import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
/// Form field allowing to select labels (i.e. correspondent, documentType)
/// [T] is the label type (e.g. [DocumentType], [Correspondent], ...), [R] is the return type (e.g. [CorrespondentQuery], ...).
///
class LabelFormField<T extends Label, R extends IdQueryParameter> extends StatefulWidget {
class LabelFormField<T extends Label, R extends IdQueryParameter>
extends StatefulWidget {
final Widget prefixIcon;
final Map<int, T> state;
final FormBuilderState? formBuilderState;
@@ -57,18 +58,19 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
void initState() {
super.initState();
_showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id);
_textEditingController =
TextEditingController(text: widget.state[widget.initialValue?.id]?.name ?? '')
..addListener(() {
setState(() {
_showCreationSuffixIcon = widget.state.values
.where((item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(),
))
.isEmpty;
});
setState(() => _showClearSuffixIcon = _textEditingController.text.isNotEmpty);
});
_textEditingController = TextEditingController(
text: widget.state[widget.initialValue?.id]?.name ?? '')
..addListener(() {
setState(() {
_showCreationSuffixIcon = widget.state.values
.where((item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(),
))
.isEmpty;
});
setState(() =>
_showClearSuffixIcon = _textEditingController.text.isNotEmpty);
});
}
@override
@@ -79,18 +81,22 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
child: Text(
S.of(context).labelFormFieldNoItemsFoundText,
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
style:
TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
),
),
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null),
name: widget.name,
itemBuilder: (context, suggestion) => ListTile(
title: Text(widget.state[suggestion.id]?.name ?? S.of(context).labelNotAssignedText),
title: Text(widget.state[suggestion.id]?.name ??
S.of(context).labelNotAssignedText),
),
suggestionsCallback: (pattern) {
final List<IdQueryParameter> suggestions = widget.state.keys
.where((item) =>
widget.state[item]!.name.toLowerCase().startsWith(pattern.toLowerCase()) ||
widget.state[item]!.name
.toLowerCase()
.startsWith(pattern.toLowerCase()) ||
pattern.isEmpty)
.map((id) => widget.queryParameterIdBuilder(id))
.toList();
@@ -117,8 +123,9 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
return widget.state[suggestion.id]?.name ?? "";
},
direction: AxisDirection.up,
onSuggestionSelected: (suggestion) =>
widget.formBuilderState?.fields[widget.name]?.didChange(suggestion as R),
onSuggestionSelected: (suggestion) => widget
.formBuilderState?.fields[widget.name]
?.didChange(suggestion as R),
);
}
@@ -127,8 +134,8 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
return IconButton(
onPressed: () => Navigator.of(context)
.push<T>(MaterialPageRoute(
builder: (context) =>
widget.labelCreationWidgetBuilder!(_textEditingController.text)))
builder: (context) => widget
.labelCreationWidgetBuilder!(_textEditingController.text)))
.then((value) {
if (value != null) {
// If new label has been created, set form field value and text of this form field and unfocus keyboard (we assume user is done).
@@ -155,7 +162,8 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
}
void _reset() {
widget.formBuilderState?.fields[widget.name]?.didChange(widget.queryParameterIdBuilder(null));
widget.formBuilderState?.fields[widget.name]
?.didChange(widget.queryParameterIdBuilder(null));
_textEditingController.clear();
}

View File

@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.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/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
@@ -33,11 +36,11 @@ class LabelItem<T extends Label> extends StatelessWidget {
subtitle: content,
leading: leading,
onTap: () => onOpenEditPage(label),
trailing: _buildDocumentCountWidget(context),
trailing: _buildReferencedDocumentsWidget(context),
);
}
Widget _buildDocumentCountWidget(BuildContext context) {
Widget _buildReferencedDocumentsWidget(BuildContext context) {
return TextButton.icon(
label: const Icon(Icons.link),
icon: Text(_formatDocumentCount(label.documentCount)),
@@ -50,8 +53,10 @@ class LabelItem<T extends Label> extends StatelessWidget {
MaterialPageRoute(
builder: (context) => LabelBlocProvider(
child: BlocProvider(
create: (context) =>
DocumentsCubit(getIt<DocumentRepository>())..updateFilter(filter: filter),
create: (context) => DocumentsCubit(
getIt<DocumentRepository>(),
getIt<GlobalErrorCubit>())
..updateFilter(filter: filter),
child: LinkedDocumentsPreview(filter: filter),
),
),

View File

@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:paperless_mobile/features/labels/view/widgets/linked_documents_preview.dart';
class LabelListTile<T extends Label> extends StatelessWidget {
final T label;
final DocumentFilter Function(Label) filterBuilder;
final void Function() onOpenEditPage;
const LabelListTile(
this.label, {
super.key,
required this.filterBuilder,
required this.onOpenEditPage,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: (label is Tag)
? CircleAvatar(
backgroundColor: (label as Tag).color,
)
: null,
title: Text(label.name),
onTap: onOpenEditPage,
trailing: _buildDocumentCountWidget(context),
subtitle: Text(
(label.match?.isEmpty ?? true) ? "-" : label.match!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
Widget _buildDocumentCountWidget(BuildContext context) {
return TextButton.icon(
label: const Icon(Icons.link),
icon: Text(_formatDocumentCount(label.documentCount)),
onPressed: (label.documentCount ?? 0) == 0
? null
: () {
final filter = filterBuilder(label);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LabelBlocProvider(
child: BlocProvider(
create: (context) =>
DocumentsCubit(getIt<DocumentRepository>())..updateFilter(filter: filter),
child: LinkedDocumentsPreview(filter: filter),
),
),
),
);
},
);
}
String _formatDocumentCount(int? count) {
if ((count ?? 0) > 99) {
return "99+";
}
return (count ?? 0).toString().padLeft(3);
}
}

View File

@@ -1,14 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
class LabelTabView<T extends Label> extends StatelessWidget {
final LabelCubit<T> cubit;
final DocumentFilter Function(Label) filterBuilder;
final void Function(T) onOpenEditPage;
final void Function() onOpenAddNewPage;
/// Displayed as the subtitle of the [ListTile]
final Widget Function(T)? contentBuilder;
@@ -16,6 +20,10 @@ class LabelTabView<T extends Label> extends StatelessWidget {
/// Displayed as the leading widget of the [ListTile]
final Widget Function(T)? leadingBuilder;
/// Shown on empty State
final String emptyStateDescription;
final String emptyStateActionButtonLabel;
const LabelTabView({
super.key,
required this.cubit,
@@ -23,27 +31,55 @@ class LabelTabView<T extends Label> extends StatelessWidget {
this.contentBuilder,
this.leadingBuilder,
required this.onOpenEditPage,
required this.emptyStateDescription,
required this.onOpenAddNewPage,
required this.emptyStateActionButtonLabel,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<Cubit<Map<int, T>>, Map<int, T>>(
bloc: cubit,
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
final labels = state.values.toList()..sort();
if (state == ConnectivityState.notConnected) {
return const OfflineWidget();
}
return RefreshIndicator(
onRefresh: cubit.initialize,
child: ListView(
children: labels
.map((l) => LabelItem<T>(
name: l.name,
content: contentBuilder?.call(l) ?? Text(l.match ?? '-'),
onOpenEditPage: onOpenEditPage,
filterBuilder: filterBuilder,
leading: leadingBuilder?.call(l),
label: l,
))
.toList(),
child: BlocBuilder<Cubit<Map<int, T>>, Map<int, T>>(
bloc: cubit,
builder: (context, state) {
final labels = state.values.toList()..sort();
if (labels.isEmpty) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
emptyStateDescription,
textAlign: TextAlign.center,
),
TextButton(
onPressed: onOpenAddNewPage,
child: Text(emptyStateActionButtonLabel),
)
].padded(),
),
);
}
return ListView(
children: labels
.map((l) => LabelItem<T>(
name: l.name,
content:
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
onOpenEditPage: onOpenEditPage,
filterBuilder: filterBuilder,
leading: leadingBuilder?.call(l),
label: l,
))
.toList(),
);
},
),
);
},

View File

@@ -20,7 +20,8 @@ class LinkedDocumentsPreview extends StatefulWidget {
}
class _LinkedDocumentsPreviewState extends State<LinkedDocumentsPreview> {
final PagingController<int, DocumentModel> _pagingController = PagingController(firstPageKey: 1);
final _pagingController =
PagingController<int, DocumentModel>(firstPageKey: 1);
@override
void initState() {
@@ -37,25 +38,43 @@ class _LinkedDocumentsPreviewState extends State<LinkedDocumentsPreview> {
body: BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) {
_pagingController.itemList = state.documents;
return CustomScrollView(
slivers: [
DocumentListView(
onTap: (doc) {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctxt) => LabelBlocProvider(
child: BlocProvider.value(
value: BlocProvider.of<DocumentsCubit>(context),
child: DocumentDetailsPage(documentId: doc.id)),
),
return Column(
children: [
Text(
S.of(context).referencedDocumentsReadOnlyHintText,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption,
),
Expanded(
child: CustomScrollView(
slivers: [
DocumentListView(
isLabelClickable: false,
onTap: (doc) {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctxt) => LabelBlocProvider(
child: BlocProvider.value(
value: BlocProvider.of<DocumentsCubit>(context),
child: DocumentDetailsPage(
documentId: doc.id,
allowEdit: false,
isLabelClickable: false,
),
),
),
),
);
},
pagingController: _pagingController,
state: state,
onSelected: BlocProvider.of<DocumentsCubit>(context)
.toggleDocumentSelection,
hasInternetConnection: true,
),
);
},
pagingController: _pagingController,
state: state,
onSelected: BlocProvider.of<DocumentsCubit>(context).toggleDocumentSelection,
hasInternetConnection: true,
],
),
),
],
);