mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 14:07:59 -06:00
WIP - More decoupling of data layer from ui layer
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/di_initializer.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';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
|
||||
class GlobalStateBlocProvider extends StatelessWidget {
|
||||
final List<BlocProvider> additionalProviders;
|
||||
final Widget child;
|
||||
const GlobalStateBlocProvider({
|
||||
super.key,
|
||||
this.additionalProviders = const [],
|
||||
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>()),
|
||||
...additionalProviders,
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,43 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
|
||||
abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
final PaperlessLabelsApi labelsApi;
|
||||
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
final LabelRepository<T> _repository;
|
||||
|
||||
LabelCubit(this.labelsApi) : super(LabelState.initial());
|
||||
late StreamSubscription _subscription;
|
||||
|
||||
@protected
|
||||
void loadFrom(Iterable<T> items) {
|
||||
emit(
|
||||
LabelState(
|
||||
isLoaded: true,
|
||||
labels: Map.fromIterable(items, key: (e) => (e as T).id!),
|
||||
),
|
||||
LabelCubit(this._repository) : super(LabelState.initial()) {
|
||||
_subscription = _repository.labels.listen(
|
||||
(update) => emit(LabelState(isLoaded: true, labels: update)),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Adds [item] to the current state. A new state is automatically pushed
|
||||
/// due to the subscription to the repository, which updates the state on
|
||||
/// operation.
|
||||
///
|
||||
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,
|
||||
),
|
||||
);
|
||||
final addedItem = await _repository.create(item);
|
||||
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,
|
||||
),
|
||||
);
|
||||
final updatedItem = await _repository.update(item);
|
||||
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),
|
||||
);
|
||||
await _repository.delete(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +45,9 @@ abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
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);
|
||||
@override
|
||||
Future<void> close() {
|
||||
_subscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class CorrespondentBlocProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const CorrespondentBlocProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class DocumentTypeBlocProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const DocumentTypeBlocProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/features/labels/bloc/providers/labels_bloc_provider.dart
Normal file
39
lib/features/labels/bloc/providers/labels_bloc_provider.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class LabelsBlocProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const LabelsBlocProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<LabelCubit<StoragePath>>(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<Correspondent>>(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<DocumentType>>(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<Tag>>(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class StoragePathBlocProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const StoragePathBlocProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/features/labels/bloc/providers/tag_bloc_provider.dart
Normal file
20
lib/features/labels/bloc/providers/tag_bloc_provider.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class TagBlocProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const TagBlocProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class CorrespondentCubit extends LabelCubit<Correspondent> {
|
||||
CorrespondentCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getCorrespondents().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Correspondent> save(Correspondent item) =>
|
||||
labelsApi.saveCorrespondent(item);
|
||||
|
||||
@override
|
||||
Future<Correspondent> update(Correspondent item) =>
|
||||
labelsApi.updateCorrespondent(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(Correspondent item) => labelsApi.deleteCorrespondent(item);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddCorrespondentPage extends StatelessWidget {
|
||||
final String? initalValue;
|
||||
const AddCorrespondentPage({Key? key, this.initalValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<Correspondent>(
|
||||
addLabelStr: S.of(context).addCorrespondentPageTitle,
|
||||
fromJson: Correspondent.fromJson,
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
initialName: initalValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditCorrespondentPage extends StatelessWidget {
|
||||
final Correspondent correspondent;
|
||||
const EditCorrespondentPage({super.key, required this.correspondent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<Correspondent>(
|
||||
label: correspondent,
|
||||
onSubmit: BlocProvider.of<CorrespondentCubit>(context).replace,
|
||||
onDelete: (correspondent) => _onDelete(context, correspondent),
|
||||
fromJson: Correspondent.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(
|
||||
BuildContext context,
|
||||
Correspondent correspondent,
|
||||
) async {
|
||||
try {
|
||||
await BlocProvider.of<CorrespondentCubit>(context).remove(correspondent);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.correspondent.id == correspondent.id) {
|
||||
await cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
correspondent: const CorrespondentQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
@@ -22,22 +23,25 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addCorrespondentToFilter(context),
|
||||
child: Text(
|
||||
(state.getLabel(correspondentId)?.name) ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
return CorrespondentBlocProvider(
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child:
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addCorrespondentToFilter(context),
|
||||
child: Text(
|
||||
(state.getLabel(correspondentId)?.name) ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class DocumentTypeCubit extends LabelCubit<DocumentType> {
|
||||
DocumentTypeCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
labelsApi.getDocumentTypes().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentType> save(DocumentType item) =>
|
||||
labelsApi.saveDocumentType(item);
|
||||
|
||||
@override
|
||||
Future<DocumentType> update(DocumentType item) =>
|
||||
labelsApi.updateDocumentType(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(DocumentType item) => labelsApi.deleteDocumentType(item);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddDocumentTypePage extends StatelessWidget {
|
||||
final String? initialName;
|
||||
const AddDocumentTypePage({Key? key, this.initialName}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<DocumentType>(
|
||||
addLabelStr: S.of(context).addDocumentTypePageTitle,
|
||||
fromJson: DocumentType.fromJson,
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
initialName: initialName,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
const EditDocumentTypePage({super.key, required this.documentType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<DocumentType>(
|
||||
label: documentType,
|
||||
onSubmit: BlocProvider.of<DocumentTypeCubit>(context).replace,
|
||||
onDelete: (docType) => _onDelete(docType, context),
|
||||
fromJson: DocumentType.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(DocumentType docType, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentTypeCubit>(context).remove(docType);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.documentType.id == docType.id) {
|
||||
cubit.updateFilter(
|
||||
filter: cubit.state.filter
|
||||
.copyWith(documentType: const DocumentTypeQuery.unset()),
|
||||
);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} finally {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
@@ -19,20 +20,26 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => _addDocumentTypeToFilter(context),
|
||||
child: BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.labels[documentTypeId]?.toString() ?? "-",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2!
|
||||
.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||
);
|
||||
},
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => _addDocumentTypeToFilter(context),
|
||||
child:
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.labels[documentTypeId]?.toString() ?? "-",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2!
|
||||
.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class StoragePathCubit extends LabelCubit<StoragePath> {
|
||||
StoragePathCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getStoragePaths().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StoragePath> save(StoragePath item) => labelsApi.saveStoragePath(item);
|
||||
|
||||
@override
|
||||
Future<StoragePath> update(StoragePath item) =>
|
||||
labelsApi.updateStoragePath(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(StoragePath item) => labelsApi.deleteStoragePath(item);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddStoragePathPage extends StatelessWidget {
|
||||
final String? initalValue;
|
||||
const AddStoragePathPage({Key? key, this.initalValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<StoragePath>(
|
||||
addLabelStr: S.of(context).addStoragePathPageTitle,
|
||||
fromJson: StoragePath.fromJson,
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
initialName: initalValue,
|
||||
additionalFields: const [
|
||||
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
|
||||
SizedBox(height: 120.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
final StoragePath storagePath;
|
||||
const EditStoragePathPage({super.key, required this.storagePath});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<StoragePath>(
|
||||
label: storagePath,
|
||||
onSubmit: BlocProvider.of<StoragePathCubit>(context).replace,
|
||||
onDelete: (correspondent) => _onDelete(correspondent, context),
|
||||
fromJson: StoragePath.fromJson,
|
||||
additionalFields: [
|
||||
StoragePathAutofillFormBuilderField(
|
||||
name: StoragePath.pathKey,
|
||||
initialValue: storagePath.path,
|
||||
),
|
||||
const SizedBox(height: 120.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(StoragePath path, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<StoragePathCubit>(context).remove(path);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.storagePath.id == path.id) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
storagePath: const StoragePathQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class StoragePathWidget extends StatelessWidget {
|
||||
@@ -22,22 +23,27 @@ class StoragePathWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addStoragePathToFilter(context),
|
||||
child: Text(
|
||||
state.getLabel(pathId)?.name ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addStoragePathToFilter(context),
|
||||
child: Text(
|
||||
state.getLabel(pathId)?.name ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class TagCubit extends LabelCubit<Tag> {
|
||||
TagCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getTags().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Tag> save(Tag item) => labelsApi.saveTag(item);
|
||||
|
||||
@override
|
||||
Future<Tag> update(Tag item) => labelsApi.updateTag(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(Tag item) => labelsApi.deleteTag(item);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
final String? initialValue;
|
||||
const AddTagPage({Key? key, this.initialValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<Tag>(
|
||||
addLabelStr: S.of(context).addTagPageTitle,
|
||||
fromJson: Tag.fromJson,
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
initialName: initialValue,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
name: Tag.colorKey,
|
||||
valueTransformer: (color) => "#${color?.value.toRadixString(16)}",
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.materialPicker,
|
||||
initialValue: null,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditTagPage extends StatelessWidget {
|
||||
final Tag tag;
|
||||
|
||||
const EditTagPage({super.key, required this.tag});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<Tag>(
|
||||
label: tag,
|
||||
onSubmit: (tag) async {
|
||||
await BlocProvider.of<TagCubit>(context).replace(tag);
|
||||
},
|
||||
onDelete: (tag) => _onDelete(tag, context),
|
||||
fromJson: Tag.fromJson,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
initialValue: tag.color,
|
||||
name: Tag.colorKey,
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.blockPicker,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
initialValue: tag.isInboxTag,
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(Tag tag, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<TagCubit>(context).remove(tag);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
final currentFilter = cubit.state.filter;
|
||||
late DocumentFilter updatedFilter = currentFilter;
|
||||
if (currentFilter.tags is IdsTagsQuery) {
|
||||
if ((currentFilter.tags as IdsTagsQuery).includedIds.contains(tag.id)) {
|
||||
updatedFilter = currentFilter.copyWith(
|
||||
tags: (currentFilter.tags as IdsTagsQuery).withIdsRemoved(
|
||||
[tag.id!],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
cubit.updateFilter(filter: updatedFilter);
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@ 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_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.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/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class TagFormField extends StatefulWidget {
|
||||
@@ -41,11 +44,13 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final state = BlocProvider.of<TagCubit>(context).state;
|
||||
_textEditingController = TextEditingController()
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_showCreationSuffixIcon = state.labels.values
|
||||
_showCreationSuffixIcon = BlocProvider.of<LabelCubit<Tag>>(context)
|
||||
.state
|
||||
.labels
|
||||
.values
|
||||
.where(
|
||||
(item) => item.name.toLowerCase().startsWith(
|
||||
_textEditingController.text.toLowerCase(),
|
||||
@@ -61,117 +66,122 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TagCubit, LabelState<Tag>>(
|
||||
builder: (context, tagState) {
|
||||
return FormBuilderField<TagsQuery>(
|
||||
builder: (field) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypeAheadField<int>(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.label_outline,
|
||||
return TagBlocProvider(
|
||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, tagState) {
|
||||
return FormBuilderField<TagsQuery>(
|
||||
builder: (field) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypeAheadField<int>(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.label_outline,
|
||||
),
|
||||
suffixIcon: _buildSuffixIcon(context, field),
|
||||
labelText: S.of(context).documentTagsPropertyLabel,
|
||||
hintText: S.of(context).tagFormFieldSearchHintText,
|
||||
),
|
||||
suffixIcon: _buildSuffixIcon(context, field),
|
||||
labelText: S.of(context).documentTagsPropertyLabel,
|
||||
hintText: S.of(context).tagFormFieldSearchHintText,
|
||||
controller: _textEditingController,
|
||||
),
|
||||
controller: _textEditingController,
|
||||
),
|
||||
suggestionsCallback: (query) {
|
||||
final suggestions = tagState.labels.values
|
||||
.where((element) => element.name
|
||||
.toLowerCase()
|
||||
.startsWith(query.toLowerCase()))
|
||||
.map((e) => e.id!)
|
||||
.toList();
|
||||
if (field.value is IdsTagsQuery) {
|
||||
suggestions.removeWhere((element) =>
|
||||
(field.value as IdsTagsQuery).ids.contains(element));
|
||||
}
|
||||
if (widget.notAssignedSelectable &&
|
||||
field.value is! OnlyNotAssignedTagsQuery) {
|
||||
suggestions.insert(0, _onlyNotAssignedId);
|
||||
}
|
||||
if (widget.anyAssignedSelectable &&
|
||||
field.value is! AnyAssignedTagsQuery) {
|
||||
suggestions.insert(0, _anyAssignedId);
|
||||
}
|
||||
return suggestions;
|
||||
},
|
||||
getImmediateSuggestions: true,
|
||||
animationStart: 1,
|
||||
itemBuilder: (context, data) {
|
||||
if (data == _onlyNotAssignedId) {
|
||||
suggestionsCallback: (query) {
|
||||
final suggestions = tagState.labels.values
|
||||
.where((element) => element.name
|
||||
.toLowerCase()
|
||||
.startsWith(query.toLowerCase()))
|
||||
.map((e) => e.id!)
|
||||
.toList();
|
||||
if (field.value is IdsTagsQuery) {
|
||||
suggestions.removeWhere((element) =>
|
||||
(field.value as IdsTagsQuery)
|
||||
.ids
|
||||
.contains(element));
|
||||
}
|
||||
if (widget.notAssignedSelectable &&
|
||||
field.value is! OnlyNotAssignedTagsQuery) {
|
||||
suggestions.insert(0, _onlyNotAssignedId);
|
||||
}
|
||||
if (widget.anyAssignedSelectable &&
|
||||
field.value is! AnyAssignedTagsQuery) {
|
||||
suggestions.insert(0, _anyAssignedId);
|
||||
}
|
||||
return suggestions;
|
||||
},
|
||||
getImmediateSuggestions: true,
|
||||
animationStart: 1,
|
||||
itemBuilder: (context, data) {
|
||||
if (data == _onlyNotAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelNotAssignedText),
|
||||
);
|
||||
} else if (data == _anyAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelAnyAssignedText),
|
||||
);
|
||||
}
|
||||
final tag = tagState.getLabel(data)!;
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelNotAssignedText),
|
||||
leading: Icon(
|
||||
Icons.circle,
|
||||
color: tag.color,
|
||||
),
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
);
|
||||
} else if (data == _anyAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelAnyAssignedText),
|
||||
);
|
||||
}
|
||||
final tag = tagState.getLabel(data)!;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.circle,
|
||||
color: tag.color,
|
||||
),
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (id) {
|
||||
if (id == _onlyNotAssignedId) {
|
||||
//Not assigned tag
|
||||
field.didChange(const OnlyNotAssignedTagsQuery());
|
||||
return;
|
||||
} else if (id == _anyAssignedId) {
|
||||
field.didChange(const AnyAssignedTagsQuery());
|
||||
} else {
|
||||
final tagsQuery = field.value is IdsTagsQuery
|
||||
? field.value as IdsTagsQuery
|
||||
: const IdsTagsQuery();
|
||||
field.didChange(tagsQuery
|
||||
.withIdQueriesAdded([IncludeTagIdQuery(id)]));
|
||||
}
|
||||
_textEditingController.clear();
|
||||
},
|
||||
direction: AxisDirection.up,
|
||||
),
|
||||
if (field.value is OnlyNotAssignedTagsQuery) ...[
|
||||
_buildNotAssignedTag(field)
|
||||
] else if (field.value is AnyAssignedTagsQuery) ...[
|
||||
_buildAnyAssignedTag(field)
|
||||
] else ...[
|
||||
// field.value is IdsTagsQuery
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
spacing: 8.0,
|
||||
children: ((field.value as IdsTagsQuery).queries)
|
||||
.map(
|
||||
(query) => _buildTag(
|
||||
field,
|
||||
query,
|
||||
tagState.getLabel(query.id),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
onSuggestionSelected: (id) {
|
||||
if (id == _onlyNotAssignedId) {
|
||||
//Not assigned tag
|
||||
field.didChange(const OnlyNotAssignedTagsQuery());
|
||||
return;
|
||||
} else if (id == _anyAssignedId) {
|
||||
field.didChange(const AnyAssignedTagsQuery());
|
||||
} else {
|
||||
final tagsQuery = field.value is IdsTagsQuery
|
||||
? field.value as IdsTagsQuery
|
||||
: const IdsTagsQuery();
|
||||
field.didChange(tagsQuery
|
||||
.withIdQueriesAdded([IncludeTagIdQuery(id)]));
|
||||
}
|
||||
_textEditingController.clear();
|
||||
},
|
||||
direction: AxisDirection.up,
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
initialValue: widget.initialValue ?? const IdsTagsQuery(),
|
||||
name: widget.name,
|
||||
);
|
||||
},
|
||||
if (field.value is OnlyNotAssignedTagsQuery) ...[
|
||||
_buildNotAssignedTag(field)
|
||||
] else if (field.value is AnyAssignedTagsQuery) ...[
|
||||
_buildAnyAssignedTag(field)
|
||||
] else ...[
|
||||
// field.value is IdsTagsQuery
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
spacing: 8.0,
|
||||
children: ((field.value as IdsTagsQuery).queries)
|
||||
.map(
|
||||
(query) => _buildTag(
|
||||
field,
|
||||
query,
|
||||
tagState.getLabel(query.id),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
initialValue: widget.initialValue ?? const IdsTagsQuery(),
|
||||
name: widget.name,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,8 +209,8 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
void _onAddTag(BuildContext context, FormFieldState<TagsQuery> field) async {
|
||||
final Tag? tag = await Navigator.of(context).push<Tag>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<TagCubit>(context),
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
child: AddTagPage(initialValue: _textEditingController.text),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
|
||||
|
||||
class TagsWidget extends StatefulWidget {
|
||||
@@ -30,36 +31,38 @@ class TagsWidget extends StatefulWidget {
|
||||
class _TagsWidgetState extends State<TagsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TagCubit, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
final children = widget.tagIds
|
||||
.where((id) => state.labels.containsKey(id))
|
||||
.map(
|
||||
(id) => TagWidget(
|
||||
tag: state.getLabel(id)!,
|
||||
afterTagTapped: widget.afterTagTapped,
|
||||
isClickable: widget.isClickable,
|
||||
isSelected: widget.isSelectedPredicate(id),
|
||||
onSelected: () => widget.onTagSelected(id),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
if (widget.isMultiLine) {
|
||||
return Wrap(
|
||||
runAlignment: WrapAlignment.start,
|
||||
children: children,
|
||||
runSpacing: 8,
|
||||
spacing: 4,
|
||||
);
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
return TagBlocProvider(
|
||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
final children = widget.tagIds
|
||||
.where((id) => state.labels.containsKey(id))
|
||||
.map(
|
||||
(id) => TagWidget(
|
||||
tag: state.getLabel(id)!,
|
||||
afterTagTapped: widget.afterTagTapped,
|
||||
isClickable: widget.isClickable,
|
||||
isSelected: widget.isSelectedPredicate(id),
|
||||
onSelected: () => widget.onTagSelected(id),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
if (widget.isMultiLine) {
|
||||
return Wrap(
|
||||
runAlignment: WrapAlignment.start,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
runSpacing: 8,
|
||||
spacing: 4,
|
||||
);
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user