fix: Add labels to each cubit using repositories and state properties, remove label cubits

This commit is contained in:
Anton Stubenbord
2023-04-04 20:30:25 +02:00
parent 78fbd042a6
commit a2388b014b
95 changed files with 4790 additions and 1823 deletions

View File

@@ -1,6 +1,5 @@
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/placeholder/document_grid_loading_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
@@ -25,7 +24,13 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
final void Function(int? id)? onDocumentTypeSelected;
final void Function(int? id)? onStoragePathSelected;
bool get showLoadingPlaceholder => (!hasLoaded && isLoading);
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
bool get showLoadingPlaceholder => !hasLoaded && isLoading;
const AdaptiveDocumentsView({
super.key,
this.selectedDocumentIds = const [],
@@ -42,6 +47,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
required this.isLoading,
required this.hasLoaded,
this.enableHeroAnimation = true,
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
});
AdaptiveDocumentsView.fromPagedState(
@@ -58,6 +67,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
required this.hasInternetConnection,
this.viewType = ViewType.list,
this.selectedDocumentIds = const [],
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
}) : documents = state.documents,
isLoading = state.isLoading,
hasLoaded = state.hasLoaded;
@@ -80,6 +93,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
super.enableHeroAnimation,
required super.isLoading,
required super.hasLoaded,
required super.correspondents,
required super.documentTypes,
required super.tags,
required super.storagePaths,
});
@override
@@ -96,27 +113,29 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildListView() {
if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget.sliver();
return const DocumentsListLoadingWidget.sliver();
}
return SliverList(
delegate: SliverChildBuilderDelegate(
childCount: documents.length,
(context, index) {
final document = documents.elementAt(index);
return LabelRepositoriesProvider(
child: DocumentListItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
),
return DocumentListItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
),
@@ -133,21 +152,23 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
childCount: documents.length,
(context, index) {
final document = documents.elementAt(index);
return LabelRepositoriesProvider(
child: DocumentDetailedItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
highlights: document.searchHit?.highlights,
),
return DocumentDetailedItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
highlights: document.searchHit?.highlights,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
),
@@ -180,6 +201,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
);
@@ -205,6 +230,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
super.selectedDocumentIds,
super.viewType,
super.enableHeroAnimation = true,
required super.correspondents,
required super.documentTypes,
required super.tags,
required super.storagePaths,
});
@override
@@ -231,20 +260,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
itemCount: documents.length,
itemBuilder: (context, index) {
final document = documents.elementAt(index);
return LabelRepositoriesProvider(
child: DocumentListItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
),
return DocumentListItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
);
@@ -252,7 +283,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildFullView() {
if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget();
return const DocumentsListLoadingWidget();
}
return ListView.builder(
@@ -263,20 +294,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
itemCount: documents.length,
itemBuilder: (context, index) {
final document = documents.elementAt(index);
return LabelRepositoriesProvider(
child: DocumentDetailedItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
),
return DocumentDetailedItem(
isLabelClickable: isLabelClickable,
document: document,
onTap: onTap,
isSelected: selectedDocumentIds.contains(document.id),
onSelected: onSelected,
isSelectionActive: selectedDocumentIds.isNotEmpty,
onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
);
@@ -284,7 +317,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildGridView() {
if (showLoadingPlaceholder) {
return DocumentGridLoadingWidget();
return const DocumentGridLoadingWidget();
}
return GridView.builder(
padding: EdgeInsets.zero,
@@ -311,6 +344,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
);
},
);

View File

@@ -1,8 +1,10 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
@@ -26,6 +28,10 @@ class DocumentDetailedItem extends DocumentItem {
super.onStoragePathSelected,
super.onTagSelected,
super.onTap,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
});
@override
@@ -113,7 +119,7 @@ class DocumentDetailedItem extends DocumentItem {
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
correspondentId: document.correspondent,
correspondent: context.read<DocumentsCubit>().correspondent,
),
],
).paddedLTRB(8, 0, 8, 4),

View File

@@ -21,6 +21,10 @@ class DocumentGridItem extends DocumentItem {
super.onTagSelected,
super.onTap,
required super.enableHeroAnimation,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
});
@override
@@ -54,10 +58,10 @@ class DocumentGridItem extends DocumentItem {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CorrespondentWidget(
correspondentId: document.correspondent,
correspondent: correspondents[document.correspondent],
),
DocumentTypeWidget(
documentTypeId: document.documentType,
documentType: documentTypes[document.documentType],
),
Text(
document.title,
@@ -67,7 +71,7 @@ class DocumentGridItem extends DocumentItem {
),
const Spacer(),
TagsWidget(
tagIds: document.tags,
tags: document.tags.map((e) => tags[e]!).toList(),
isMultiLine: false,
onTagSelected: onTagSelected,
),

View File

@@ -10,6 +10,11 @@ abstract class DocumentItem extends StatelessWidget {
final bool isLabelClickable;
final bool enableHeroAnimation;
final Map<int, Tag> tags;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, StoragePath> storagePaths;
final void Function(int tagId)? onTagSelected;
final void Function(int? correspondentId)? onCorrespondentSelected;
final void Function(int? documentTypeId)? onDocumentTypeSelected;
@@ -28,5 +33,9 @@ abstract class DocumentItem extends StatelessWidget {
this.onDocumentTypeSelected,
this.onStoragePathSelected,
required this.enableHeroAnimation,
required this.tags,
required this.correspondents,
required this.documentTypes,
required this.storagePaths,
});
}

View File

@@ -5,7 +5,6 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
@@ -25,113 +24,109 @@ class DocumentListItem extends DocumentItem {
super.onTagSelected,
super.onTap,
super.enableHeroAnimation = true,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
});
@override
Widget build(BuildContext context) {
return DocumentTypeBlocProvider(
child: Material(
child: ListTile(
dense: true,
selected: isSelected,
onTap: () => _onTap(),
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
onLongPress: () => onSelected?.call(document),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
AbsorbPointer(
absorbing: isSelectionActive,
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondentId: document.correspondent,
onSelected: onCorrespondentSelected,
),
return Material(
child: ListTile(
dense: true,
selected: isSelected,
onTap: () => _onTap(),
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
onLongPress: () => onSelected?.call(document),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
AbsorbPointer(
absorbing: isSelectionActive,
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondent: correspondents[document.correspondent],
onSelected: onCorrespondentSelected,
),
],
),
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
AbsorbPointer(
absorbing: isSelectionActive,
child: TagsWidget(
isClickable: isLabelClickable,
tagIds: document.tags,
isMultiLine: false,
onTagSelected: (id) => onTagSelected?.call(id),
),
)
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: BlocBuilder<LabelCubit<DocumentType>,
LabelState<DocumentType>>(
builder: (context, docTypes) {
return RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: DateFormat.yMMMd().format(document.created),
style: Theme.of(context)
.textTheme
.labelSmall
?.apply(color: Colors.grey),
children: document.documentType != null
? [
const TextSpan(text: '\u30FB'),
TextSpan(
text: docTypes
.labels[document.documentType]?.name,
),
]
: null,
),
);
},
)
// Row(
// children: [
// Text(
// DateFormat.yMMMd().format(document.created),
// style: Theme.of(context)
// .textTheme
// .bodySmall
// ?.apply(color: Colors.grey),
// ),
// if (document.documentType != null) ...[
// Text("\u30FB"),
// DocumentTypeWidget(
// documentTypeId: document.documentType,
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
// color: Colors.grey,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// ],
// ),
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
document: document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: enableHeroAnimation,
],
),
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
AbsorbPointer(
absorbing: isSelectionActive,
child: TagsWidget(
isClickable: isLabelClickable,
tags: document.tags.map((e) => tags[e]!).toList(),
isMultiLine: false,
onTagSelected: (id) => onTagSelected?.call(id),
),
)
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: DateFormat.yMMMd().format(document.created),
style: Theme.of(context)
.textTheme
.labelSmall
?.apply(color: Colors.grey),
children: document.documentType != null
? [
const TextSpan(text: '\u30FB'),
TextSpan(
text: documentTypes[document.documentType]?.name,
),
]
: null,
),
),
contentPadding: const EdgeInsets.all(8.0),
// Row(
// children: [
// Text(
// DateFormat.yMMMd().format(document.created),
// style: Theme.of(context)
// .textTheme
// .bodySmall
// ?.apply(color: Colors.grey),
// ),
// if (document.documentType != null) ...[
// Text("\u30FB"),
// DocumentTypeWidget(
// documentTypeId: document.documentType,
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
// color: Colors.grey,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// ],
// ),
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
document: document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: enableHeroAnimation,
),
),
),
contentPadding: const EdgeInsets.all(8.0),
),
);
}

View File

@@ -71,7 +71,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
.paddedOnly(left: 4, right: 4),
_buildBulkEditStoragePathChip(context)
.paddedOnly(left: 4, right: 4),
// _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
],
),
),
@@ -100,9 +100,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) {
return BlocProvider(
create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
@@ -112,8 +109,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<Correspondent>(
initialValue: initialValue,
title: "Bulk edit correspondent",
availableOptionsSelector: (state) =>
state.correspondentOptions,
availableOptionsSelector: (state) => state.correspondents,
formFieldLabel: S.of(context)!.correspondent,
formFieldPrefixIcon: const Icon(Icons.person_outline),
onSubmit: (selectedId) async {
@@ -152,9 +148,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) {
return BlocProvider(
create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
@@ -164,8 +157,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<DocumentType>(
initialValue: initialValue,
title: "Bulk edit document type",
availableOptionsSelector: (state) =>
state.documentTypeOptions,
availableOptionsSelector: (state) => state.documentTypes,
formFieldLabel: S.of(context)!.documentType,
formFieldPrefixIcon: const Icon(Icons.person_outline),
onSubmit: (selectedId) async {
@@ -204,9 +196,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) {
return BlocProvider(
create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
@@ -216,7 +205,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<StoragePath>(
initialValue: initialValue,
title: "Bulk edit storage path",
availableOptionsSelector: (state) => state.storagePathOptions,
availableOptionsSelector: (state) => state.storagePaths,
formFieldLabel: S.of(context)!.storagePath,
formFieldPrefixIcon: const Icon(Icons.folder_open_outlined),
onSubmit: (selectedId) async {
@@ -246,14 +235,11 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
topRight: Radius.circular(16),
),
),
isScrollControlled: false,
isScrollControlled: true,
context: context,
builder: (_) {
return BlocProvider(
create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),
context.read(),

View File

@@ -43,14 +43,7 @@ class SortDocumentsButton extends StatelessWidget {
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
),
),
BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
),
create: (context) => LabelCubit(context.read()),
),
],
child: SortFieldSelectionBottomSheet(