WIP - More decoupling of data layer from ui layer

This commit is contained in:
Anton Stubenbord
2022-12-09 00:54:39 +01:00
parent 75fa2f7713
commit c9694fa8d0
87 changed files with 2508 additions and 1879 deletions

View File

@@ -1,112 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class AddLabelPage<T extends Label> extends StatefulWidget {
final String? initialName;
final String addLabelStr;
final T Function(Map<String, dynamic> json) fromJson;
final LabelCubit<T> cubit;
final List<Widget> additionalFields;
const AddLabelPage({
Key? key,
this.initialName,
required this.addLabelStr,
required this.fromJson,
required this.cubit,
this.additionalFields = const [],
}) : super(key: key);
@override
State<AddLabelPage> createState() => _AddLabelPageState<T>();
}
class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
final _formKey = GlobalKey<FormBuilderState>();
PaperlessValidationErrors _errors = {};
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text(widget.addLabelStr),
),
floatingActionButton: Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0,
child: FloatingActionButton.extended(
icon: const Icon(Icons.add),
label: Text(S.of(context).genericActionCreateLabel),
onPressed: _onSubmit,
),
),
body: FormBuilder(
key: _formKey,
child: ListView(
children: [
FormBuilderTextField(
autovalidateMode: AutovalidateMode.onUserInteraction,
name: Label.nameKey,
decoration: InputDecoration(
labelText: S.of(context).labelNamePropertyLabel,
errorText: _errors[Label.nameKey],
),
initialValue: widget.initialName,
validator: FormBuilderValidators.required(),
onChanged: (val) => setState(() => _errors = {}),
),
FormBuilderTextField(
autovalidateMode: AutovalidateMode.onUserInteraction,
name: Label.matchKey,
decoration: InputDecoration(
labelText: S.of(context).labelMatchPropertyLabel,
),
onChanged: (val) => setState(() => _errors = {}),
),
FormBuilderDropdown<int?>(
name: Label.matchingAlgorithmKey,
initialValue: MatchingAlgorithm.anyWord.value,
decoration: InputDecoration(
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
errorText: _errors[Label.matchingAlgorithmKey],
),
onChanged: (val) => setState(() => _errors = {}),
items: MatchingAlgorithm.values
.map((algo) => DropdownMenuItem<int?>(
child: Text(algo.name), //TODO: INTL
value: algo.value))
.toList(),
),
FormBuilderCheckbox(
name: Label.isInsensitiveKey,
initialValue: true,
title: Text(S.of(context).labelIsInsensivitePropertyLabel),
),
...widget.additionalFields,
].padded(),
),
),
);
}
void _onSubmit() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
try {
final label = await widget.cubit
.add(widget.fromJson(_formKey.currentState!.value));
Navigator.pop(context, label);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
} on PaperlessValidationErrors catch (json) {
setState(() => _errors = json);
}
}
}
}

View File

@@ -1,156 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class EditLabelPage<T extends Label> extends StatefulWidget {
final T label;
final Future<void> Function(T) onSubmit;
final Future<void> Function(T) onDelete;
final T Function(JSON) fromJson;
final List<Widget> additionalFields;
const EditLabelPage({
Key? key,
required this.label,
required this.fromJson,
required this.onSubmit,
required this.onDelete,
this.additionalFields = const [],
}) : super(key: key);
@override
State<EditLabelPage> createState() => _EditLabelPageState<T>();
}
class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
final _formKey = GlobalKey<FormBuilderState>();
PaperlessValidationErrors _errors = {};
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(S.of(context).genericActionEditLabel),
actions: [
IconButton(
onPressed: _onDelete,
icon: const Icon(Icons.delete),
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.update),
label: Text(S.of(context).genericActionUpdateLabel),
onPressed: _onSubmit,
),
body: FormBuilder(
key: _formKey,
child: ListView(
children: [
FormBuilderTextField(
name: Label.nameKey,
decoration: InputDecoration(
labelText: S.of(context).labelNamePropertyLabel,
errorText: _errors[Label.nameKey],
),
validator: FormBuilderValidators.required(),
initialValue: widget.label.name,
onChanged: (val) => setState(() => _errors = {}),
),
FormBuilderTextField(
name: Label.matchKey,
decoration: InputDecoration(
labelText: S.of(context).labelMatchPropertyLabel,
errorText: _errors[Label.matchKey],
),
initialValue: widget.label.match,
onChanged: (val) => setState(() => _errors = {}),
),
FormBuilderDropdown<int?>(
name: Label.matchingAlgorithmKey,
initialValue: widget.label.matchingAlgorithm?.value ??
MatchingAlgorithm.allWords.value,
decoration: InputDecoration(
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
errorText: _errors[Label.matchingAlgorithmKey],
),
onChanged: (val) => setState(() => _errors = {}),
items: MatchingAlgorithm.values
.map(
(algo) => DropdownMenuItem<int?>(
child: Text(algo.name), //TODO: INTL
value: algo.value,
),
)
.toList(),
),
FormBuilderCheckbox(
name: Label.isInsensitiveKey,
initialValue: widget.label.isInsensitive,
title: Text(S.of(context).labelIsInsensivitePropertyLabel),
),
...widget.additionalFields,
].padded(),
),
),
);
}
void _onDelete() {
if ((widget.label.documentCount ?? 0) > 0) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(S.of(context).editLabelPageConfirmDeletionDialogTitle),
content: Text(
S.of(context).editLabelPageDeletionDialogText,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(S.of(context).genericActionCancelLabel),
),
TextButton(
onPressed: () {
Navigator.pop(context);
widget.onDelete(widget.label);
},
child: Text(
S.of(context).genericActionDeleteLabel,
style: TextStyle(color: Theme.of(context).errorColor),
),
),
],
),
);
} else {
widget.onDelete(widget.label);
}
}
void _onSubmit() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
try {
final mergedJson = {
...widget.label.toJson(),
..._formKey.currentState!.value
};
await widget.onSubmit(widget.fromJson(mergedJson));
Navigator.pop(context);
} on PaperlessValidationErrors catch (errorMessages) {
setState(() => _errors = errorMessages);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
}

View File

@@ -1,22 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/core/repository/label_repository.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';
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_correspondent_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/pages/edit_correspondent_page.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart';
import 'package:paperless_mobile/features/labels/document_type/view/pages/edit_document_type_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/pages/edit_storage_path_page.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart';
import 'package:paperless_mobile/features/labels/tags/view/pages/edit_tag_page.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -35,10 +30,6 @@ class _LabelsPageState extends State<LabelsPage>
@override
void initState() {
super.initState();
BlocProvider.of<CorrespondentCubit>(context).initialize();
BlocProvider.of<DocumentTypeCubit>(context).initialize();
BlocProvider.of<TagCubit>(context).initialize();
_tabController = TabController(length: 4, vsync: this)
..addListener(() => setState(() => _currentIndex = _tabController.index));
}
@@ -60,7 +51,12 @@ class _LabelsPageState extends State<LabelsPage>
),
actions: [
IconButton(
onPressed: _onAddPressed,
onPressed: [
_openAddCorrespondentPage,
_openAddDocumentTypePage,
_openAddTagPage,
_openAddStoragePathPage,
][_currentIndex],
icon: const Icon(Icons.add),
)
],
@@ -104,69 +100,87 @@ class _LabelsPageState extends State<LabelsPage>
body: TabBarView(
controller: _tabController,
children: [
LabelTabView<Correspondent>(
cubit: BlocProvider.of<CorrespondentCubit>(context),
filterBuilder: (label) => DocumentFilter(
correspondent: CorrespondentQuery.fromId(label.id),
pageSize: label.documentCount ?? 0,
BlocProvider(
create: (context) => LabelCubit(
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
),
child: LabelTabView<Correspondent>(
filterBuilder: (label) => DocumentFilter(
correspondent: CorrespondentQuery.fromId(label.id),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditCorrespondentPage,
emptyStateActionButtonLabel:
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
emptyStateDescription: S
.of(context)
.labelsPageCorrespondentEmptyStateDescriptionText,
onAddNew: _openAddCorrespondentPage,
),
onOpenEditPage: _openEditCorrespondentPage,
emptyStateActionButtonLabel:
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
emptyStateDescription: S
.of(context)
.labelsPageCorrespondentEmptyStateDescriptionText,
onOpenAddNewPage: _onAddPressed,
),
LabelTabView<DocumentType>(
cubit: BlocProvider.of<DocumentTypeCubit>(context),
filterBuilder: (label) => DocumentFilter(
documentType: DocumentTypeQuery.fromId(label.id),
pageSize: label.documentCount ?? 0,
BlocProvider(
create: (context) => LabelCubit(
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
),
child: LabelTabView<DocumentType>(
filterBuilder: (label) => DocumentFilter(
documentType: DocumentTypeQuery.fromId(label.id),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditDocumentTypePage,
emptyStateActionButtonLabel:
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
emptyStateDescription: S
.of(context)
.labelsPageDocumentTypeEmptyStateDescriptionText,
onAddNew: _openAddDocumentTypePage,
),
onOpenEditPage: _openEditDocumentTypePage,
emptyStateActionButtonLabel:
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
emptyStateDescription:
S.of(context).labelsPageDocumentTypeEmptyStateDescriptionText,
onOpenAddNewPage: _onAddPressed,
),
LabelTabView<Tag>(
cubit: BlocProvider.of<TagCubit>(context),
filterBuilder: (label) => DocumentFilter(
tags: IdsTagsQuery.fromIds([label.id!]),
pageSize: label.documentCount ?? 0,
BlocProvider(
create: (context) => LabelCubit<Tag>(
RepositoryProvider.of<LabelRepository<Tag>>(context),
),
onOpenEditPage: _openEditTagPage,
leadingBuilder: (t) => CircleAvatar(
backgroundColor: t.color,
child: t.isInboxTag ?? false
? Icon(
Icons.inbox,
color: t.textColor,
)
: null,
child: LabelTabView<Tag>(
filterBuilder: (label) => DocumentFilter(
tags: IdsTagsQuery.fromIds([label.id!]),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditTagPage,
leadingBuilder: (t) => CircleAvatar(
backgroundColor: t.color,
child: t.isInboxTag ?? false
? Icon(
Icons.inbox,
color: t.textColor,
)
: null,
),
contentBuilder: (t) => Text(t.match ?? ''),
emptyStateActionButtonLabel:
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
emptyStateDescription:
S.of(context).labelsPageTagsEmptyStateDescriptionText,
onAddNew: _openAddTagPage,
),
contentBuilder: (t) => Text(t.match ?? ''),
emptyStateActionButtonLabel:
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
emptyStateDescription:
S.of(context).labelsPageTagsEmptyStateDescriptionText,
onOpenAddNewPage: _onAddPressed,
),
LabelTabView<StoragePath>(
cubit: BlocProvider.of<StoragePathCubit>(context),
onOpenEditPage: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter(
storagePath: StoragePathQuery.fromId(label.id),
pageSize: label.documentCount ?? 0,
BlocProvider(
create: (context) => LabelCubit<StoragePath>(
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
),
child: LabelTabView<StoragePath>(
onEdit: _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,
onAddNew: _openAddStoragePathPage,
),
contentBuilder: (path) => Text(path.path ?? ""),
emptyStateActionButtonLabel:
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
emptyStateDescription:
S.of(context).labelsPageStoragePathEmptyStateDescriptionText,
onOpenAddNewPage: _onAddPressed,
),
],
),
@@ -178,12 +192,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GlobalStateBlocProvider(
additionalProviders: [
BlocProvider<DocumentsCubit>.value(
value: BlocProvider.of<DocumentsCubit>(context),
),
],
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
child: EditCorrespondentPage(correspondent: correspondent),
),
),
@@ -194,12 +204,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GlobalStateBlocProvider(
additionalProviders: [
BlocProvider<DocumentsCubit>.value(
value: BlocProvider.of<DocumentsCubit>(context),
),
],
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
child: EditDocumentTypePage(documentType: docType),
),
),
@@ -210,12 +216,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GlobalStateBlocProvider(
additionalProviders: [
BlocProvider<DocumentsCubit>.value(
value: BlocProvider.of<DocumentsCubit>(context),
),
],
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
child: EditTagPage(tag: tag),
),
),
@@ -226,37 +228,61 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GlobalStateBlocProvider(
additionalProviders: [
BlocProvider<DocumentsCubit>.value(
value: getIt<DocumentsCubit>(),
),
],
child: EditStoragePathPage(storagePath: path),
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
child: EditStoragePathPage(
storagePath: path,
),
),
),
);
}
void _onAddPressed() {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
late final Widget page;
switch (_currentIndex) {
case 0:
page = const AddCorrespondentPage();
break;
case 1:
page = const AddDocumentTypePage();
break;
case 2:
page = const AddTagPage();
break;
case 3:
page = const AddStoragePathPage();
}
return GlobalStateBlocProvider(child: page);
},
));
void _openAddCorrespondentPage() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
child: const AddCorrespondentPage(),
),
),
);
}
void _openAddDocumentTypePage() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
child: const AddDocumentTypePage(),
),
),
);
}
void _openAddTagPage() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
child: const AddTagPage(),
),
),
);
}
void _openAddStoragePathPage() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
child: const AddStoragePathPage(),
),
),
);
}
}

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
@@ -46,12 +45,11 @@ class LabelItem<T extends Label> extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GlobalStateBlocProvider(
additionalProviders: [
BlocProvider<LinkedDocumentsCubit>.value(
value: getIt<LinkedDocumentsCubit>()
..initialize(filter)),
],
builder: (context) => BlocProvider.value(
value: LinkedDocumentsCubit(
getIt<PaperlessDocumentsApi>(),
filter,
),
child: const LinkedDocumentsPage(),
),
),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
@@ -9,10 +10,9 @@ 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;
final void Function(T) onEdit;
final void Function() onAddNew;
/// Displayed as the subtitle of the [ListTile]
final Widget Function(T)? contentBuilder;
@@ -26,13 +26,12 @@ class LabelTabView<T extends Label> extends StatelessWidget {
const LabelTabView({
super.key,
required this.cubit,
required this.filterBuilder,
this.contentBuilder,
this.leadingBuilder,
required this.onOpenEditPage,
required this.onEdit,
required this.emptyStateDescription,
required this.onOpenAddNewPage,
required this.onAddNew,
required this.emptyStateActionButtonLabel,
});
@@ -43,44 +42,40 @@ class LabelTabView<T extends Label> extends StatelessWidget {
if (state == ConnectivityState.notConnected) {
return const OfflineWidget();
}
return RefreshIndicator(
onRefresh: cubit.initialize,
child: BlocBuilder<Cubit<LabelState<T>>, LabelState<T>>(
bloc: cubit,
builder: (context, state) {
final labels = state.labels.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(),
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
builder: (context, state) {
final labels = state.labels.values.toList()..sort();
if (labels.isEmpty) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
emptyStateDescription,
textAlign: TextAlign.center,
),
TextButton(
onPressed: onAddNew,
child: Text(emptyStateActionButtonLabel),
)
].padded(),
),
);
},
),
}
return ListView(
children: labels
.map((l) => LabelItem<T>(
name: l.name,
content:
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
onOpenEditPage: onEdit,
filterBuilder: filterBuilder,
leading: leadingBuilder?.call(l),
label: l,
))
.toList(),
);
},
);
},
);