mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 22:07:53 -06:00
WIP - more decoupling of blocs
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -8,27 +7,19 @@ import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.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/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentEditPage extends StatefulWidget {
|
||||
final DocumentModel document;
|
||||
final FutureOr<void> Function(DocumentModel updatedDocument) onEdit;
|
||||
|
||||
const DocumentEditPage({
|
||||
Key? key,
|
||||
required this.document,
|
||||
required this.onEdit,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -43,150 +34,133 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
static const fkCreatedDate = "createdAtDate";
|
||||
static const fkStoragePath = 'storagePath';
|
||||
|
||||
late Future<Uint8List> documentBytes;
|
||||
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
bool _isSubmitLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
documentBytes =
|
||||
getIt<PaperlessDocumentsApi>().getPreview(widget.document.id);
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EditDocumentCubit, EditDocumentState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => _onSubmit(state.document),
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context).genericActionSaveLabel),
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentEditPageTitle),
|
||||
bottom: _isSubmitLoading
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
extendBody: true,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(children: [
|
||||
_buildTitleFormField(state.document.title).padded(),
|
||||
_buildCreatedAtFormField(state.document.created).padded(),
|
||||
_buildDocumentTypeFormField(
|
||||
state.document.documentType, state.documentTypes)
|
||||
.padded(),
|
||||
_buildCorrespondentFormField(
|
||||
state.document.correspondent, state.correspondents)
|
||||
.padded(),
|
||||
_buildStoragePathFormField(
|
||||
state.document.storagePath, state.storagePaths)
|
||||
.padded(),
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(state.document.tags),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
selectableOptions: state.tags,
|
||||
).padded(),
|
||||
]),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LabelsBlocProvider(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: _onSubmit,
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context).genericActionSaveLabel),
|
||||
Widget _buildStoragePathFormField(
|
||||
int? initialId, Map<int, StoragePath> options) {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
state: options,
|
||||
initialValue: StoragePathQuery.fromId(initialId),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField(
|
||||
int? initialId, Map<int, Correspondent> options) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentEditPageTitle),
|
||||
bottom: _isSubmitLoading
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: options,
|
||||
initialValue: CorrespondentQuery.fromId(initialId),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField(
|
||||
int? initialId, Map<int, DocumentType> options) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
extendBody: true,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(children: [
|
||||
_buildTitleFormField().padded(),
|
||||
_buildCreatedAtFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(widget.document.tags),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
).padded(),
|
||||
]),
|
||||
),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: DocumentTypeQuery.fromId(initialId),
|
||||
state: options,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>
|
||||
_buildStoragePathFormField() {
|
||||
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue: StoragePathQuery.fromId(widget.document.storagePath),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>
|
||||
_buildCorrespondentFormField() {
|
||||
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue:
|
||||
CorrespondentQuery.fromId(widget.document.correspondent),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>
|
||||
_buildDocumentTypeFormField() {
|
||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: DocumentTypeQuery.fromId(widget.document.documentType),
|
||||
state: state.labels,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit() async {
|
||||
Future<void> _onSubmit(DocumentModel document) async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final values = _formKey.currentState!.value;
|
||||
var updatedDocument = widget.document.copyWith(
|
||||
var mergedDocument = document.copyWith(
|
||||
title: values[fkTitle],
|
||||
created: values[fkCreatedDate],
|
||||
overwriteDocumentType: true,
|
||||
@@ -201,9 +175,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
setState(() {
|
||||
_isSubmitLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await widget.onEdit(updatedDocument);
|
||||
await BlocProvider.of<EditDocumentCubit>(context)
|
||||
.updateDocument(mergedDocument);
|
||||
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
@@ -216,18 +190,18 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTitleFormField() {
|
||||
Widget _buildTitleFormField(String? initialTitle) {
|
||||
return FormBuilderTextField(
|
||||
name: fkTitle,
|
||||
validator: FormBuilderValidators.required(),
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).documentTitlePropertyLabel),
|
||||
),
|
||||
initialValue: widget.document.title,
|
||||
initialValue: initialTitle,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCreatedAtFormField() {
|
||||
Widget _buildCreatedAtFormField(DateTime? initialCreatedAtDate) {
|
||||
return FormBuilderDateTimePicker(
|
||||
inputType: InputType.date,
|
||||
name: fkCreatedDate,
|
||||
@@ -235,7 +209,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
prefixIcon: const Icon(Icons.calendar_month_outlined),
|
||||
label: Text(S.of(context).documentCreatedPropertyLabel),
|
||||
),
|
||||
initialValue: widget.document.created,
|
||||
initialValue: initialCreatedAtDate,
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format
|
||||
initialEntryMode: DatePickerEntryMode.calendar,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
@@ -24,7 +24,6 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||
|
||||
class DocumentsPage extends StatefulWidget {
|
||||
const DocumentsPage({Key? key}) : super(key: key);
|
||||
@@ -35,16 +34,17 @@ class DocumentsPage extends StatefulWidget {
|
||||
|
||||
class _DocumentsPageState extends State<DocumentsPage> {
|
||||
late final DocumentsCubit _documentsCubit;
|
||||
late final SavedViewCubit _savedViewCubit;
|
||||
|
||||
final _pagingController = PagingController<int, DocumentModel>(
|
||||
firstPageKey: 1,
|
||||
);
|
||||
|
||||
final _filterPanelController = PanelController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
_savedViewCubit = BlocProvider.of<SavedViewCubit>(context);
|
||||
try {
|
||||
_documentsCubit.load();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
@@ -59,97 +59,69 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final pageCount = _documentsCubit.state
|
||||
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
try {
|
||||
await _documentsCubit.loadMore();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(DocumentModel model) {
|
||||
_documentsCubit.toggleDocumentSelection(model);
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await _documentsCubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_filterPanelController.isPanelOpen) {
|
||||
FocusScope.of(context).unfocus();
|
||||
_filterPanelController.close();
|
||||
return false;
|
||||
}
|
||||
if (_documentsCubit.state.selection.isNotEmpty) {
|
||||
_documentsCubit.resetSelection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
_documentsCubit.load();
|
||||
},
|
||||
child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
_documentsCubit.load();
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
drawer: BlocProvider.value(
|
||||
value: BlocProvider.of<AuthenticationCubit>(context),
|
||||
child: InfoDrawer(
|
||||
afterInboxClosed: () => _documentsCubit.reload(),
|
||||
),
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: SlidingUpPanel(
|
||||
backdropEnabled: true,
|
||||
parallaxEnabled: true,
|
||||
parallaxOffset: .5,
|
||||
controller: _filterPanelController,
|
||||
defaultPanelState: PanelState.CLOSED,
|
||||
minHeight: 48,
|
||||
maxHeight: (MediaQuery.of(context).size.height * 3) / 4,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
body: _buildBody(connectivityState),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
panelBuilder: (scrollController) =>
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
panelController: _filterPanelController,
|
||||
scrollController: scrollController,
|
||||
initialFilter: state.filter,
|
||||
onFilterChanged: (filter) =>
|
||||
_documentsCubit.updateFilter(filter: filter),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||
return Badge(
|
||||
toAnimate: false,
|
||||
showBadge: appliedFiltersCount > 0,
|
||||
badgeContent: appliedFiltersCount > 0
|
||||
? Text(state.filter.appliedFiltersCount.toString())
|
||||
: null,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt),
|
||||
onPressed: _openDocumentFilter,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: _buildBody(connectivityState));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openDocumentFilter() async {
|
||||
final filter = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => SizedBox(
|
||||
height: MediaQuery.of(context).size.height - kToolbarHeight - 16,
|
||||
child: LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
initialFilter: _documentsCubit.state.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDismissible: true,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16.0),
|
||||
topRight: Radius.circular(16.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (filter != null) {
|
||||
_documentsCubit.updateFilter(filter: filter);
|
||||
_savedViewCubit.resetSelection();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody(ConnectivityState connectivityState) {
|
||||
@@ -193,6 +165,10 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
child = SliverToBoxAdapter(
|
||||
child: DocumentsEmptyState(
|
||||
state: state,
|
||||
onReset: () {
|
||||
_documentsCubit.updateFilter();
|
||||
_savedViewCubit.resetSelection();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -201,51 +177,45 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
onRefresh: _onRefresh,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
RepositoryProvider.of<SavedViewRepository>(context)),
|
||||
child: BlocListener<SavedViewCubit, SavedViewState>(
|
||||
listener: (context, state) {
|
||||
try {
|
||||
if (state.selectedSavedViewId == null) {
|
||||
_documentsCubit.updateFilter();
|
||||
} else {
|
||||
final newFilter = state
|
||||
.value[state.selectedSavedViewId]
|
||||
?.toDocumentFilter();
|
||||
if (newFilter != null) {
|
||||
_documentsCubit.updateFilter(filter: newFilter);
|
||||
}
|
||||
BlocListener<SavedViewCubit, SavedViewState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.selectedSavedViewId !=
|
||||
current.selectedSavedViewId,
|
||||
listener: (context, state) {
|
||||
try {
|
||||
if (state.selectedSavedViewId == null) {
|
||||
_documentsCubit.updateFilter();
|
||||
} else {
|
||||
final newFilter = state
|
||||
.value[state.selectedSavedViewId]
|
||||
?.toDocumentFilter();
|
||||
if (newFilter != null) {
|
||||
_documentsCubit.updateFilter(filter: newFilter);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
child: DocumentsPageAppBar(
|
||||
actions: [
|
||||
const SortDocumentsButton(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
settings.preferredViewType == ViewType.grid
|
||||
? Icons.list
|
||||
: Icons.grid_view,
|
||||
),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<ApplicationSettingsCubit>(
|
||||
context)
|
||||
.setViewType(
|
||||
settings.preferredViewType.toggle()),
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
child: DocumentsPageAppBar(
|
||||
actions: [
|
||||
const SortDocumentsButton(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
settings.preferredViewType == ViewType.grid
|
||||
? Icons.list
|
||||
: Icons.grid_view,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<ApplicationSettingsCubit>(context)
|
||||
.setViewType(
|
||||
settings.preferredViewType.toggle(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child,
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 4,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -296,4 +266,31 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final pageCount = _documentsCubit.state
|
||||
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
try {
|
||||
await _documentsCubit.loadMore();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(DocumentModel model) {
|
||||
_documentsCubit.toggleDocumentSelection(model);
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await _documentsCubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class DocumentsEmptyState extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
final VoidCallback onReset;
|
||||
const DocumentsEmptyState({
|
||||
Key? key,
|
||||
required this.state,
|
||||
required this.onReset,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -22,10 +24,7 @@ class DocumentsEmptyState extends StatelessWidget {
|
||||
subtitle: S.of(context).documentsPageEmptyStateNothingHereText,
|
||||
bottomChild: state.filter != DocumentFilter.initial
|
||||
? TextButton(
|
||||
onPressed: () async {
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
},
|
||||
onPressed: onReset,
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageResetFilterLabel,
|
||||
),
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/query_type_form_field.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/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||
|
||||
enum DateRangeSelection { before, after }
|
||||
|
||||
class DocumentFilterPanel extends StatefulWidget {
|
||||
final PanelController panelController;
|
||||
final ScrollController scrollController;
|
||||
|
||||
final DocumentFilter initialFilter;
|
||||
|
||||
final void Function(DocumentFilter filter) onFilterChanged;
|
||||
const DocumentFilterPanel({
|
||||
Key? key,
|
||||
required this.panelController,
|
||||
required this.scrollController,
|
||||
required this.onFilterChanged,
|
||||
required this.initialFilter,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -60,33 +50,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const radius = Radius.circular(16);
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
topLeft: radius,
|
||||
topRight: radius,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_buildDragLine(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label:
|
||||
Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
_buildDraggableResetHeader(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -101,9 +75,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
const SizedBox(
|
||||
height: 16.0,
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
@@ -111,34 +82,30 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
topRight: Radius.circular(16.0),
|
||||
),
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(S.of(context).documentsFilterPageSearchLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0)),
|
||||
_buildQueryFormField(),
|
||||
).paddedOnly(left: 8.0),
|
||||
_buildQueryFormField().padded(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
Text(S.of(context).documentsFilterPageAdvancedLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0, top: 8.0)),
|
||||
_buildCreatedDateRangePickerFormField().padded(),
|
||||
_buildAddedDateRangePickerFormField().padded(),
|
||||
).padded(),
|
||||
_buildCreatedDateRangePickerFormField(),
|
||||
_buildAddedDateRangePickerFormField(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowCreation: false,
|
||||
).padded(),
|
||||
_buildTagsFormField()
|
||||
.paddedSymmetrically(horizontal: 8, vertical: 4.0),
|
||||
// Required in order for the storage path field to be visible when typing
|
||||
const SizedBox(
|
||||
height: 150,
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -147,13 +114,39 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<Tag>, LabelState<Tag>> _buildTagsFormField() {
|
||||
return BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
return TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowCreation: false,
|
||||
selectableOptions: state.labels,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Stack _buildDraggableResetHeader() {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_buildDragLine(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _resetFilter(BuildContext context) async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
if (!widget.panelController.isPanelClosed) {
|
||||
widget.panelController.close();
|
||||
}
|
||||
Navigator.pop(context, DocumentFilter.initial);
|
||||
}
|
||||
|
||||
//TODO: Check if the blocs can be found in the context, otherwise just provide repository and create new bloc inside LabelFormField!
|
||||
@@ -238,14 +231,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
),
|
||||
initialValue: widget.initialFilter.queryText,
|
||||
).padded();
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateRangePickerHelper(String formFieldKey) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
const spacer = SizedBox(width: 8.0);
|
||||
return SizedBox(
|
||||
height: 64,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastSevenDaysLabel,
|
||||
@@ -258,7 +254,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastMonthLabel,
|
||||
@@ -275,7 +272,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastThreeMonthsLabel,
|
||||
@@ -295,7 +293,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastYearLabel,
|
||||
@@ -316,6 +315,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
},
|
||||
),
|
||||
spacer,
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -358,12 +358,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
).paddedSymmetrically(horizontal: 8, vertical: 4.0),
|
||||
_buildDateRangePickerHelper(fkCreatedAt),
|
||||
],
|
||||
);
|
||||
@@ -393,7 +393,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
child: child!,
|
||||
),
|
||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||
format: DateFormat.yMMMd(),
|
||||
fieldStartLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText:
|
||||
@@ -406,11 +406,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
_formKey.currentState?.fields[fkAddedAt]?.didChange(null),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[fkAddedAt]?.didChange(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
).paddedSymmetrically(horizontal: 8),
|
||||
const SizedBox(height: 4.0),
|
||||
_buildDateRangePickerHelper(fkAddedAt),
|
||||
],
|
||||
@@ -429,28 +430,33 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
}
|
||||
|
||||
void _onApplyFilter() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
_formKey.currentState?.save();
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
final v = _formKey.currentState!.value;
|
||||
final docCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
DocumentFilter newFilter = docCubit.state.filter.copyWith(
|
||||
DocumentFilter newFilter = DocumentFilter(
|
||||
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end,
|
||||
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start,
|
||||
correspondent: v[fkCorrespondent] as CorrespondentQuery?,
|
||||
documentType: v[fkDocumentType] as DocumentTypeQuery?,
|
||||
storagePath: v[fkStoragePath] as StoragePathQuery?,
|
||||
tags: v[DocumentModel.tagsKey] as TagsQuery?,
|
||||
page: 1,
|
||||
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
documentType: v[fkDocumentType] as DocumentTypeQuery? ??
|
||||
DocumentFilter.initial.documentType,
|
||||
storagePath: v[fkStoragePath] as StoragePathQuery? ??
|
||||
DocumentFilter.initial.storagePath,
|
||||
tags: v[DocumentModel.tagsKey] as TagsQuery? ??
|
||||
DocumentFilter.initial.tags,
|
||||
queryText: v[fkQuery] as String?,
|
||||
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end,
|
||||
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
||||
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
||||
asnQuery: widget.initialFilter.asnQuery,
|
||||
page: 1,
|
||||
pageSize: widget.initialFilter.pageSize,
|
||||
sortField: widget.initialFilter.sortField,
|
||||
sortOrder: widget.initialFilter.sortOrder,
|
||||
);
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: newFilter);
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.panelController.close();
|
||||
Navigator.pop(context, newFilter);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -1,6 +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/extensions/flutter_extensions.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/generated/l10n.dart';
|
||||
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
@@ -46,30 +49,58 @@ class _SortFieldSelectionBottomSheetState
|
||||
S.of(context).documentsPageOrderByLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
textAlign: TextAlign.start,
|
||||
).padded(
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
onPressed: () => widget.onSubmit(
|
||||
_currentSortField,
|
||||
_currentSortOrder,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.onSubmit(
|
||||
_currentSortField,
|
||||
_currentSortOrder,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).paddedSymmetrically(horizontal: 16, vertical: 8.0),
|
||||
Column(
|
||||
children: SortField.values.map(_buildSortOption).toList(),
|
||||
children: [
|
||||
_buildSortOption(SortField.archiveSerialNumber),
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return _buildSortOption(
|
||||
SortField.correspondentName,
|
||||
enabled: state.labels.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildSortOption(SortField.title),
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return _buildSortOption(
|
||||
SortField.documentType,
|
||||
enabled: state.labels.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildSortOption(SortField.created),
|
||||
_buildSortOption(SortField.added),
|
||||
_buildSortOption(SortField.modified),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSortOption(
|
||||
SortField field,
|
||||
) {
|
||||
Widget _buildSortOption(SortField field, {bool enabled = true}) {
|
||||
return ListTile(
|
||||
enabled: enabled,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
title: Text(
|
||||
_localizedSortField(field),
|
||||
@@ -77,6 +108,14 @@ class _SortFieldSelectionBottomSheetState
|
||||
trailing: _currentSortField == field
|
||||
? _buildOrderIcon(_currentSortOrder)
|
||||
: null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_currentSortOrder = (_currentSortField == field
|
||||
? _currentSortOrder.toggle()
|
||||
: SortOrder.descending);
|
||||
_currentSortField = field;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_selection_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
@@ -35,7 +33,7 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(false),
|
||||
flexibleSpace: _buildFlexibleArea(false, documentsState.filter),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () =>
|
||||
@@ -56,13 +54,12 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(true),
|
||||
title: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(state.count)})',
|
||||
);
|
||||
},
|
||||
flexibleSpace: _buildFlexibleArea(
|
||||
true,
|
||||
documentsState.filter,
|
||||
),
|
||||
title: Text(
|
||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(documentsState.count)})',
|
||||
),
|
||||
actions: [
|
||||
...widget.actions,
|
||||
@@ -73,14 +70,18 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFlexibleArea(bool enabled) {
|
||||
Widget _buildFlexibleArea(bool enabled, DocumentFilter filter) {
|
||||
return FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SavedViewSelectionWidget(height: 48, enabled: enabled),
|
||||
SavedViewSelectionWidget(
|
||||
height: 48,
|
||||
enabled: enabled,
|
||||
currentFilter: filter,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
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/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class SortDocumentsButton extends StatefulWidget {
|
||||
const SortDocumentsButton({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
class SortDocumentsButton extends StatelessWidget {
|
||||
const SortDocumentsButton({super.key});
|
||||
|
||||
@override
|
||||
State<SortDocumentsButton> createState() => _SortDocumentsButtonState();
|
||||
}
|
||||
|
||||
class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.sort),
|
||||
onPressed: _onOpenSortBottomSheet,
|
||||
onPressed: () => _onOpenSortBottomSheet(context),
|
||||
);
|
||||
}
|
||||
|
||||
void _onOpenSortBottomSheet() {
|
||||
void _onOpenSortBottomSheet(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
@@ -32,19 +28,41 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
heightFactor: .6,
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) =>
|
||||
BlocProvider.of<DocumentsCubit>(context).updateCurrentFilter(
|
||||
(filter) => filter.copyWith(sortField: field, sortOrder: order),
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
child: FractionallySizedBox(
|
||||
heightFactor: .6,
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) =>
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user