mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 10:08:00 -06:00
Updated onboarding, reformatted files, improved referenced documents view, updated error handling
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/bloc/label_cubit.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/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/model/matching_algorithm.dart';
|
||||
import 'package:paperless_mobile/features/labels/model/label.model.dart';
|
||||
@@ -34,7 +35,7 @@ class AddLabelPage<T extends Label> extends StatefulWidget {
|
||||
|
||||
class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
Map<String, String> _errors = {};
|
||||
PaperlessValidationErrors _errors = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -103,11 +104,12 @@ class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
try {
|
||||
final label = await widget.cubit.add(widget.fromJson(_formKey.currentState!.value));
|
||||
final label = await widget.cubit
|
||||
.add(widget.fromJson(_formKey.currentState!.value));
|
||||
Navigator.pop(context, label);
|
||||
} on ErrorMessage catch (e) {
|
||||
showSnackBar(context, translateError(context, e.code));
|
||||
} on Map<String, String> catch (json) {
|
||||
} on PaperlessValidationErrors catch (json) {
|
||||
setState(() => _errors = json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter/material.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/core/type/json.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/model/matching_algorithm.dart';
|
||||
import 'package:paperless_mobile/features/labels/model/label.model.dart';
|
||||
@@ -35,7 +35,7 @@ class EditLabelPage<T extends Label> extends StatefulWidget {
|
||||
class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
Map<String, String> _errors = {};
|
||||
PaperlessValidationErrors _errors = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -80,8 +80,8 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
),
|
||||
FormBuilderDropdown<int?>(
|
||||
name: Label.matchingAlgorithmKey,
|
||||
initialValue:
|
||||
widget.label.matchingAlgorithm?.value ?? MatchingAlgorithm.allWords.value,
|
||||
initialValue: widget.label.matchingAlgorithm?.value ??
|
||||
MatchingAlgorithm.allWords.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
|
||||
errorText: _errors[Label.matchingAlgorithmKey],
|
||||
@@ -111,12 +111,13 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
try {
|
||||
final mergedJson = {...widget.label.toJson(), ..._formKey.currentState!.value};
|
||||
final mergedJson = {
|
||||
...widget.label.toJson(),
|
||||
..._formKey.currentState!.value
|
||||
};
|
||||
await widget.onSubmit(widget.fromJson(mergedJson));
|
||||
Navigator.pop(context);
|
||||
} on ErrorMessage catch (e) {
|
||||
showSnackBar(context, translateError(context, e.code));
|
||||
} on Map<String, String> catch (errorMessages) {
|
||||
} on PaperlessValidationErrors catch (errorMessages) {
|
||||
setState(() => _errors = errorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ class LabelsPage extends StatefulWidget {
|
||||
State<LabelsPage> createState() => _LabelsPageState();
|
||||
}
|
||||
|
||||
class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateMixin {
|
||||
class _LabelsPageState extends State<LabelsPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController;
|
||||
int _currentIndex = 0;
|
||||
|
||||
@@ -54,100 +55,126 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
drawer: const InfoDrawer(),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
[
|
||||
S.of(context).labelsPageCorrespondentsTitleText,
|
||||
S.of(context).labelsPageDocumentTypesTitleText,
|
||||
S.of(context).labelsPageTagsTitleText,
|
||||
S.of(context).labelsPageStoragePathTitleText
|
||||
][_currentIndex],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onAddPressed,
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).bottomAppBarColor,
|
||||
child: TabBar(
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
return BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
drawer: const InfoDrawer(),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
[
|
||||
S.of(context).labelsPageCorrespondentsTitleText,
|
||||
S.of(context).labelsPageDocumentTypesTitleText,
|
||||
S.of(context).labelsPageTagsTitleText,
|
||||
S.of(context).labelsPageStoragePathTitleText
|
||||
][_currentIndex],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onAddPressed,
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).bottomAppBarColor,
|
||||
child: TabBar(
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
LabelTabView<Correspondent>(
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: CorrespondentQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
LabelTabView<Correspondent>(
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: CorrespondentQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
onOpenEditPage: _openEditCorrespondentPage,
|
||||
),
|
||||
LabelTabView<DocumentType>(
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: DocumentTypeQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
LabelTabView<DocumentType>(
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: DocumentTypeQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
onOpenEditPage: _openEditDocumentTypePage,
|
||||
),
|
||||
LabelTabView<Tag>(
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: TagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
LabelTabView<Tag>(
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: TagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(backgroundColor: t.color),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
onOpenEditPage: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(backgroundColor: t.color),
|
||||
),
|
||||
LabelTabView<StoragePath>(
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
onOpenEditPage: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: StoragePathQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
LabelTabView<StoragePath>(
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
onOpenEditPage: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: StoragePathQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -160,7 +187,8 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(value: BlocProvider.of<CorrespondentCubit>(context)),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context)),
|
||||
],
|
||||
child: EditCorrespondentPage(correspondent: correspondent),
|
||||
),
|
||||
@@ -175,7 +203,8 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(value: BlocProvider.of<DocumentTypeCubit>(context)),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context)),
|
||||
],
|
||||
child: EditDocumentTypePage(documentType: docType),
|
||||
),
|
||||
@@ -205,7 +234,8 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(value: BlocProvider.of<StoragePathCubit>(context)),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<StoragePathCubit>(context)),
|
||||
],
|
||||
child: EditStoragePathPage(storagePath: path),
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user