Implemented inbox (still WIP)

This commit is contained in:
Anton Stubenbord
2022-11-24 13:37:25 +01:00
parent 8e7a5dddbf
commit eb5025e8ca
44 changed files with 674 additions and 316 deletions

View File

@@ -0,0 +1,27 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
class LabelBlocProvider extends StatelessWidget {
final Widget child;
const LabelBlocProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<DocumentTypeCubit>()),
BlocProvider.value(value: getIt<CorrespondentCubit>()),
BlocProvider.value(value: getIt<TagCubit>()),
BlocProvider.value(value: getIt<StoragePathCubit>()),
BlocProvider.value(value: getIt<SavedViewCubit>()),
],
child: child,
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/repository/label_repository.dart';
abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
final LabelRepository labelRepository;
LabelCubit(this.labelRepository) : super(LabelState.initial());
@protected
void loadFrom(Iterable<T> items) {
emit(
LabelState(
isLoaded: true,
labels: Map.fromIterable(items, key: (e) => (e as T).id!),
),
);
}
Future<T> add(T item) async {
assert(item.id == null);
final addedItem = await save(item);
final newValues = {...state.labels};
newValues.putIfAbsent(addedItem.id!, () => addedItem);
emit(
LabelState(
isLoaded: true,
labels: newValues,
),
);
return addedItem;
}
Future<T> replace(T item) async {
assert(item.id != null);
final updatedItem = await update(item);
final updatedValues = {...state.labels};
updatedValues[item.id!] = updatedItem;
emit(
LabelState(
isLoaded: state.isLoaded,
labels: updatedValues,
),
);
return updatedItem;
}
Future<void> remove(T item) async {
assert(item.id != null);
if (state.labels.containsKey(item.id)) {
final deletedId = await delete(item);
final updatedValues = {...state.labels}..remove(deletedId);
emit(
LabelState(isLoaded: true, labels: updatedValues),
);
}
}
void reset() {
emit(LabelState(isLoaded: false, labels: {}));
}
Future<void> initialize();
@protected
Future<T> save(T item);
@protected
Future<T> update(T item);
@protected
Future<int> delete(T item);
}

View File

@@ -1,4 +1,4 @@
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:injectable/injectable.dart';

View File

@@ -6,6 +6,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/util.dart';
class CorrespondentWidget extends StatelessWidget {
@@ -16,7 +17,7 @@ class CorrespondentWidget extends StatelessWidget {
const CorrespondentWidget({
Key? key,
required this.correspondentId,
this.correspondentId,
this.afterSelected,
this.textColor,
this.isClickable = true,
@@ -26,12 +27,12 @@ class CorrespondentWidget extends StatelessWidget {
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: !isClickable,
child: BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>(
child: BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) {
return GestureDetector(
onTap: () => _addCorrespondentToFilter(context),
child: Text(
(state[correspondentId]?.name) ?? "-",
(state.getLabel(correspondentId)?.name) ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith(

View File

@@ -1,5 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:injectable/injectable.dart';

View File

@@ -5,6 +5,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/util.dart';
class DocumentTypeWidget extends StatelessWidget {
@@ -24,10 +25,10 @@ class DocumentTypeWidget extends StatelessWidget {
absorbing: !isClickable,
child: GestureDetector(
onTap: () => _addDocumentTypeToFilter(context),
child: BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>(
child: BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) {
return Text(
state[documentTypeId]?.toString() ?? "-",
state.labels[documentTypeId]?.toString() ?? "-",
style: Theme.of(context)
.textTheme
.bodyText2!

View File

@@ -0,0 +1,19 @@
import 'package:paperless_mobile/features/labels/model/label.model.dart';
class LabelState<T extends Label> {
LabelState.initial() : this(isLoaded: false, labels: {});
final bool isLoaded;
final Map<int, T> labels;
LabelState({
required this.isLoaded,
required this.labels,
});
T? getLabel(int? key) {
if (isLoaded) {
return labels[key];
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
@singleton

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.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/model/query_parameters/storage_path_query.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
import 'package:paperless_mobile/util.dart';
@@ -15,7 +16,7 @@ class StoragePathWidget extends StatelessWidget {
const StoragePathWidget({
Key? key,
required this.pathId,
this.pathId,
this.afterSelected,
this.textColor,
this.isClickable = true,
@@ -25,12 +26,12 @@ class StoragePathWidget extends StatelessWidget {
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: !isClickable,
child: BlocBuilder<StoragePathCubit, Map<int, StoragePath>>(
child: BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) {
return GestureDetector(
onTap: () => _addStoragePathToFilter(context),
child: Text(
(state[pathId]?.name) ?? "-",
state.getLabel(pathId)?.name ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith(

View File

@@ -1,4 +1,4 @@
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:injectable/injectable.dart';

View File

@@ -1,6 +1,7 @@
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/bloc/paperless_statistics_cubit.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/model/document_filter.dart';
@@ -21,7 +22,11 @@ class EditTagPage extends StatelessWidget {
Widget build(BuildContext context) {
return EditLabelPage<Tag>(
label: tag,
onSubmit: BlocProvider.of<TagCubit>(context).replace,
onSubmit: (tag) async {
await BlocProvider.of<TagCubit>(context).replace(tag);
//If inbox property was added/removed from tag, the number of documetns in inbox may increase/decrease.
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
},
onDelete: (tag) => _onDelete(tag, context),
fromJson: Tag.fromJson,
additionalFields: [

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart';
@@ -45,7 +46,7 @@ class _TagFormFieldState extends State<TagFormField> {
_textEditingController = TextEditingController()
..addListener(() {
setState(() {
_showCreationSuffixIcon = state.values
_showCreationSuffixIcon = state.labels.values
.where(
(item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(),
@@ -61,7 +62,7 @@ class _TagFormFieldState extends State<TagFormField> {
@override
Widget build(BuildContext context) {
return BlocBuilder<TagCubit, Map<int, Tag>>(
return BlocBuilder<TagCubit, LabelState<Tag>>(
builder: (context, tagState) {
return FormBuilderField<TagsQuery>(
builder: (field) {
@@ -81,7 +82,7 @@ class _TagFormFieldState extends State<TagFormField> {
controller: _textEditingController,
),
suggestionsCallback: (query) {
final suggestions = tagState.values
final suggestions = tagState.labels.values
.where((element) => element.name
.toLowerCase()
.startsWith(query.toLowerCase()))
@@ -113,7 +114,7 @@ class _TagFormFieldState extends State<TagFormField> {
title: Text(S.of(context).labelAnyAssignedText),
);
}
final tag = tagState[data]!;
final tag = tagState.getLabel(data)!;
return ListTile(
leading: Icon(
Icons.circle,
@@ -159,7 +160,7 @@ class _TagFormFieldState extends State<TagFormField> {
(query) => _buildTag(
field,
query,
tagState[query.id]!,
tagState.getLabel(query.id)!,
),
)
.toList(),

View File

@@ -2,6 +2,7 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
@@ -27,13 +28,13 @@ class TagsWidget extends StatefulWidget {
class _TagsWidgetState extends State<TagsWidget> {
@override
Widget build(BuildContext context) {
return BlocBuilder<TagCubit, Map<int, Tag>>(
return BlocBuilder<TagCubit, LabelState<Tag>>(
builder: (context, state) {
final children = widget.tagIds
.where((id) => state.containsKey(id))
.where((id) => state.labels.containsKey(id))
.map(
(id) => TagWidget(
tag: state[id]!,
tag: state.getLabel(id)!,
afterTagTapped: widget.afterTagTapped,
isClickable: widget.isClickable,
),

View File

@@ -2,7 +2,7 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/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';

View File

@@ -1,6 +1,7 @@
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/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/features/labels/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';
@@ -227,6 +228,7 @@ class _LabelsPageState extends State<LabelsPage>
providers: [
BlocProvider.value(value: getIt<DocumentsCubit>()),
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
],
child: EditTagPage(tag: tag),
),

View File

@@ -1,6 +1,6 @@
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/features/labels/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';

View File

@@ -1,10 +1,11 @@
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/features/labels/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/model/label_state.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
@@ -45,10 +46,10 @@ class LabelTabView<T extends Label> extends StatelessWidget {
}
return RefreshIndicator(
onRefresh: cubit.initialize,
child: BlocBuilder<Cubit<Map<int, T>>, Map<int, T>>(
child: BlocBuilder<Cubit<LabelState<T>>, LabelState<T>>(
bloc: cubit,
builder: (context, state) {
final labels = state.values.toList()..sort();
final labels = state.labels.values.toList()..sort();
if (labels.isEmpty) {
return Center(
child: Column(

View File

@@ -1,6 +1,6 @@
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/features/labels/bloc/label_bloc_provider.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/document.model.dart';