mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2026-01-31 06:25:10 -06:00
Fixed FABs stacking on form fields, some other minor improvements
This commit is contained in:
@@ -100,12 +100,19 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
/// Update filter state and automatically reload documents. Always resets page to 1.
|
/// Update filter state and automatically reload documents. Always resets page to 1.
|
||||||
/// Use [DocumentsCubit.loadMore] to load more data.
|
/// Use [DocumentsCubit.loadMore] to load more data.
|
||||||
Future<void> updateFilter({
|
Future<void> updateFilter({
|
||||||
DocumentFilter filter = DocumentFilter.initial,
|
final DocumentFilter filter = DocumentFilter.initial,
|
||||||
}) async {
|
}) async {
|
||||||
final result = await documentRepository.find(filter.copyWith(page: 1));
|
final result = await documentRepository.find(filter.copyWith(page: 1));
|
||||||
emit(DocumentsState(filter: filter, value: [result], isLoaded: true));
|
emit(DocumentsState(filter: filter, value: [result], isLoaded: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
|
||||||
|
///
|
||||||
|
Future<void> updateCurrentFilter(final DocumentFilter Function(DocumentFilter) transformFn) {
|
||||||
|
return updateFilter(filter: transformFn(state.filter));
|
||||||
|
}
|
||||||
|
|
||||||
void toggleDocumentSelection(DocumentModel model) {
|
void toggleDocumentSelection(DocumentModel model) {
|
||||||
if (state.selection.contains(model)) {
|
if (state.selection.contains(model)) {
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:flutter_paperless_mobile/features/documents/model/query_paramete
|
|||||||
import 'package:flutter_paperless_mobile/util.dart';
|
import 'package:flutter_paperless_mobile/util.dart';
|
||||||
|
|
||||||
class DocumentFilter with EquatableMixin {
|
class DocumentFilter with EquatableMixin {
|
||||||
|
static const _oneDay = Duration(days: 1);
|
||||||
static const DocumentFilter initial = DocumentFilter();
|
static const DocumentFilter initial = DocumentFilter();
|
||||||
|
|
||||||
static const DocumentFilter latestDocument = DocumentFilter(
|
static const DocumentFilter latestDocument = DocumentFilter(
|
||||||
@@ -67,20 +68,21 @@ class DocumentFilter with EquatableMixin {
|
|||||||
|
|
||||||
sb.write("&ordering=${sortOrder.queryString}${sortField.queryString}");
|
sb.write("&ordering=${sortOrder.queryString}${sortField.queryString}");
|
||||||
|
|
||||||
|
// Add/subtract one day in the following because paperless uses gt/lt not gte/lte
|
||||||
if (addedDateAfter != null) {
|
if (addedDateAfter != null) {
|
||||||
sb.write("&added__date__gt=${dateFormat.format(addedDateAfter!)}");
|
sb.write("&added__date__gt=${dateFormat.format(addedDateAfter!.subtract(_oneDay))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addedDateBefore != null) {
|
if (addedDateBefore != null) {
|
||||||
sb.write("&added__date__lt=${dateFormat.format(addedDateBefore!)}");
|
sb.write("&added__date__lt=${dateFormat.format(addedDateBefore!.add(_oneDay))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createdDateAfter != null) {
|
if (createdDateAfter != null) {
|
||||||
sb.write("&created__date__gt=${dateFormat.format(createdDateAfter!)}");
|
sb.write("&created__date__gt=${dateFormat.format(createdDateAfter!.subtract(_oneDay))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createdDateBefore != null) {
|
if (createdDateBefore != null) {
|
||||||
sb.write("&created__date__lt=${dateFormat.format(createdDateBefore!)}");
|
sb.write("&created__date__lt=${dateFormat.format(createdDateBefore!.add(_oneDay))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ abstract class IdQueryParameter extends Equatable {
|
|||||||
return "&${queryParameterKey}__isnull=$_assignmentStatus";
|
return "&${queryParameterKey}__isnull=$_assignmentStatus";
|
||||||
}
|
}
|
||||||
if (isSet) {
|
if (isSet) {
|
||||||
return "${queryParameterKey}__id=$id";
|
return "&${queryParameterKey}__id=$id";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,9 +122,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
controller: _panelController,
|
controller: _panelController,
|
||||||
defaultPanelState: PanelState.CLOSED,
|
defaultPanelState: PanelState.CLOSED,
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
maxHeight: MediaQuery.of(context).size.height -
|
maxHeight: MediaQuery.of(context).size.height - kBottomNavigationBarHeight,
|
||||||
kBottomNavigationBarHeight -
|
|
||||||
2 * kToolbarHeight,
|
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
|
|||||||
@@ -2,27 +2,26 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart';
|
import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart';
|
import 'package:flutter_paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/scan/view/document_upload_page.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/generated/l10n.dart';
|
import 'package:flutter_paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||||
@@ -62,7 +61,14 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
bool _isQueryLoading = false;
|
|
||||||
|
late final DocumentsCubit _documentsCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
|
}
|
||||||
|
|
||||||
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
|
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
|
||||||
if (start == null && end == null) {
|
if (start == null && end == null) {
|
||||||
@@ -78,7 +84,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<DocumentsCubit, DocumentsState>(
|
return ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
child: BlocConsumer<DocumentsCubit, DocumentsState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
// Set initial values, otherwise they would not automatically update.
|
// Set initial values, otherwise they would not automatically update.
|
||||||
_patchFromFilter(state.filter);
|
_patchFromFilter(state.filter);
|
||||||
@@ -86,10 +97,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return FormBuilder(
|
return FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: MediaQuery.removePadding(
|
child: ListView(
|
||||||
context: context,
|
controller: widget.scrollController,
|
||||||
removeTop: true,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -121,23 +130,19 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padded(),
|
).padded(),
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
controller: widget.scrollController,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16.0,
|
height: 16.0,
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(S.of(context).documentsFilterPageSearchLabel),
|
child: Text(S.of(context).documentsFilterPageSearchLabel),
|
||||||
).padded(),
|
).padded(const EdgeInsets.only(left: 8.0)),
|
||||||
_buildQueryFormField(state),
|
_buildQueryFormField(state),
|
||||||
_buildSortByChipsList(context, state),
|
_buildSortByChipsList(context, state),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(S.of(context).documentsFilterPageAdvancedLabel),
|
child: Text(S.of(context).documentsFilterPageAdvancedLabel),
|
||||||
).padded(),
|
).padded(const EdgeInsets.only(left: 8.0, top: 8.0)),
|
||||||
_buildCreatedDateRangePickerFormField(state).padded(),
|
_buildCreatedDateRangePickerFormField(state).padded(),
|
||||||
_buildAddedDateRangePickerFormField(state).padded(),
|
_buildAddedDateRangePickerFormField(state).padded(),
|
||||||
_buildCorrespondentFormField(state).padded(),
|
_buildCorrespondentFormField(state).padded(),
|
||||||
@@ -147,14 +152,15 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
name: DocumentModel.tagsKey,
|
name: DocumentModel.tagsKey,
|
||||||
initialValue: state.filter.tags,
|
initialValue: state.filter.tags,
|
||||||
).padded(),
|
).padded(),
|
||||||
],
|
// Required in order for the storage path field to be visible when typing
|
||||||
),
|
const SizedBox(
|
||||||
|
height: 200,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +190,23 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCorrespondentFormField(DocumentsState docState) {
|
||||||
|
return BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||||
|
formBuilderState: _formKey.currentState,
|
||||||
|
name: fkCorrespondent,
|
||||||
|
state: state,
|
||||||
|
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||||
|
initialValue: docState.filter.correspondent,
|
||||||
|
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||||
|
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||||
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStoragePathFormField(DocumentsState docState) {
|
Widget _buildStoragePathFormField(DocumentsState docState) {
|
||||||
return BlocBuilder<StoragePathCubit, Map<int, StoragePath>>(
|
return BlocBuilder<StoragePathCubit, Map<int, StoragePath>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -308,23 +331,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCorrespondentFormField(DocumentsState docState) {
|
|
||||||
return BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
|
||||||
formBuilderState: _formKey.currentState,
|
|
||||||
name: fkCorrespondent,
|
|
||||||
state: state,
|
|
||||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
|
||||||
initialValue: docState.filter.correspondent,
|
|
||||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
|
||||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCreatedDateRangePickerFormField(DocumentsState state) {
|
Widget _buildCreatedDateRangePickerFormField(DocumentsState state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -333,16 +339,21 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
state.filter.createdDateAfter,
|
state.filter.createdDateAfter,
|
||||||
state.filter.createdDateBefore,
|
state.filter.createdDateBefore,
|
||||||
),
|
),
|
||||||
pickerBuilder: (context, child) {
|
// Workaround for theme data not being correctly passed to daterangepicker, see
|
||||||
return Theme(
|
// https://github.com/flutter/flutter/issues/87580
|
||||||
data: ThemeData.light().copyWith(
|
pickerBuilder: (context, Widget? child) => Theme(
|
||||||
primaryColor: Theme.of(context).primaryColor,
|
data: Theme.of(context).copyWith(
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
buttonTheme: Theme.of(context).buttonTheme,
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
|
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||||
|
onPrimary: Theme.of(context).primaryColor,
|
||||||
|
primary: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: child!,
|
child: child!,
|
||||||
);
|
),
|
||||||
},
|
|
||||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||||
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||||
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||||
@@ -371,16 +382,21 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
state.filter.addedDateAfter,
|
state.filter.addedDateAfter,
|
||||||
state.filter.addedDateBefore,
|
state.filter.addedDateBefore,
|
||||||
),
|
),
|
||||||
pickerBuilder: (context, child) {
|
// Workaround for theme data not being correctly passed to daterangepicker, see
|
||||||
return Theme(
|
// https://github.com/flutter/flutter/issues/87580
|
||||||
data: ThemeData.light().copyWith(
|
pickerBuilder: (context, Widget? child) => Theme(
|
||||||
primaryColor: Theme.of(context).primaryColor,
|
data: Theme.of(context).copyWith(
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
buttonTheme: Theme.of(context).buttonTheme,
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
|
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||||
|
onPrimary: Theme.of(context).primaryColor,
|
||||||
|
primary: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: child!,
|
child: child!,
|
||||||
);
|
),
|
||||||
},
|
|
||||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||||
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||||
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||||
@@ -413,9 +429,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSortByChipsList(BuildContext context, DocumentsState state) {
|
Widget _buildSortByChipsList(BuildContext context, DocumentsState state) {
|
||||||
return Padding(
|
return Column(
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -435,8 +449,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
).padded();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActionChip(
|
Widget _buildActionChip(
|
||||||
@@ -481,9 +494,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onApplyFilter() {
|
void _onApplyFilter() {
|
||||||
setState(() => _isQueryLoading = true);
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
_formKey.currentState?.save();
|
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
|
||||||
final v = _formKey.currentState!.value;
|
final v = _formKey.currentState!.value;
|
||||||
final docCubit = BlocProvider.of<DocumentsCubit>(context);
|
final docCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
DocumentFilter newFilter = docCubit.state.filter.copyWith(
|
DocumentFilter newFilter = docCubit.state.filter.copyWith(
|
||||||
@@ -503,7 +514,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
widget.panelController.close();
|
widget.panelController.close();
|
||||||
setState(() => _isQueryLoading = false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,15 +43,14 @@ class CorrespondentWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _addCorrespondentToFilter(BuildContext context) {
|
void _addCorrespondentToFilter(BuildContext context) {
|
||||||
final cubit = getIt<DocumentsCubit>();
|
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
if (cubit.state.filter.correspondent.id == correspondentId) {
|
if (cubit.state.filter.correspondent.id == correspondentId) {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(correspondent: const CorrespondentQuery.unset()));
|
(filter) => filter.copyWith(correspondent: const CorrespondentQuery.unset()),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(
|
(filter) => filter.copyWith(correspondent: CorrespondentQuery.fromId(correspondentId)),
|
||||||
correspondent: CorrespondentQuery.fromId(correspondentId),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
afterSelected?.call();
|
afterSelected?.call();
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_paperless_mobile/di_initializer.dart';
|
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
||||||
|
|
||||||
class DocumentTypeWidget extends StatelessWidget {
|
class DocumentTypeWidget extends StatelessWidget {
|
||||||
final int? documentTypeId;
|
final int? documentTypeId;
|
||||||
final void Function()? afterSelected;
|
final void Function()? afterSelected;
|
||||||
|
final bool isSelectable;
|
||||||
const DocumentTypeWidget({
|
const DocumentTypeWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.documentTypeId,
|
required this.documentTypeId,
|
||||||
this.afterSelected,
|
this.afterSelected,
|
||||||
|
this.isSelectable = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return AbsorbPointer(
|
||||||
onTap: _addDocumentTypeToFilter,
|
absorbing: !isSelectable,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _addDocumentTypeToFilter(context),
|
||||||
child: BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>(
|
child: BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Text(
|
return Text(
|
||||||
@@ -26,27 +29,25 @@ class DocumentTypeWidget extends StatelessWidget {
|
|||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyText2!
|
.bodyText2!
|
||||||
.copyWith(color: Theme.of(context).colorScheme.primary),
|
.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addDocumentTypeToFilter() {
|
void _addDocumentTypeToFilter(BuildContext context) {
|
||||||
final cubit = getIt<DocumentsCubit>();
|
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
if (cubit.state.filter.documentType.id == documentTypeId) {
|
if (cubit.state.filter.documentType.id == documentTypeId) {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(documentType: const DocumentTypeQuery.unset()));
|
(filter) => filter.copyWith(documentType: const DocumentTypeQuery.unset()),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(
|
(filter) => filter.copyWith(documentType: DocumentTypeQuery.fromId(documentTypeId)),
|
||||||
documentType: DocumentTypeQuery.fromId(documentTypeId),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (afterSelected != null) {
|
|
||||||
afterSelected?.call();
|
afterSelected?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||||
@@ -43,15 +42,14 @@ class StoragePathWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _addStoragePathToFilter(BuildContext context) {
|
void _addStoragePathToFilter(BuildContext context) {
|
||||||
final cubit = getIt<DocumentsCubit>();
|
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
if (cubit.state.filter.correspondent.id == pathId) {
|
if (cubit.state.filter.correspondent.id == pathId) {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(storagePath: const StoragePathQuery.unset()));
|
(filter) => filter.copyWith(storagePath: const StoragePathQuery.unset()),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateFilter(
|
cubit.updateCurrentFilter(
|
||||||
filter: cubit.state.filter.copyWith(
|
(filter) => filter.copyWith(storagePath: StoragePathQuery.fromId(pathId)),
|
||||||
storagePath: StoragePathQuery.fromId(pathId),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
afterSelected?.call();
|
afterSelected?.call();
|
||||||
|
|||||||
@@ -51,11 +51,15 @@ class EditTagPage extends StatelessWidget {
|
|||||||
if (currentFilter.tags.ids.contains(tag.id)) {
|
if (currentFilter.tags.ids.contains(tag.id)) {
|
||||||
updatedFilter = currentFilter.copyWith(
|
updatedFilter = currentFilter.copyWith(
|
||||||
tags: TagsQuery.fromIds(
|
tags: TagsQuery.fromIds(
|
||||||
currentFilter.tags.ids.where((tagId) => tagId != tag.id).toList()));
|
currentFilter.tags.ids.where((tagId) => tagId != tag.id).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
cubit.updateFilter(filter: updatedFilter);
|
cubit.updateFilter(filter: updatedFilter);
|
||||||
} on ErrorMessage catch (error) {
|
} on ErrorMessage catch (error) {
|
||||||
showError(context, error);
|
showError(context, error);
|
||||||
|
} finally {
|
||||||
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
|||||||
import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart';
|
import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart';
|
||||||
import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||||
import 'package:flutter_paperless_mobile/core/model/error_message.dart';
|
import 'package:flutter_paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:flutter_paperless_mobile/core/type/json.dart';
|
|
||||||
import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart';
|
import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart';
|
||||||
@@ -40,15 +39,18 @@ class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.addLabelStr),
|
title: Text(widget.addLabelStr),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: Visibility(
|
||||||
|
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: Text(S.of(context).genericActionCreateLabel),
|
label: Text(S.of(context).genericActionCreateLabel),
|
||||||
onPressed: _onSubmit,
|
onPressed: _onSubmit,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
body: FormBuilder(
|
body: FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
@@ -99,7 +101,6 @@ class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onSubmit() async {
|
void _onSubmit() async {
|
||||||
log("IsValid? ${_formKey.currentState?.isValid}");
|
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
try {
|
try {
|
||||||
final label = await widget.cubit.add(widget.fromJson(_formKey.currentState!.value));
|
final label = await widget.cubit.add(widget.fromJson(_formKey.currentState!.value));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart';
|
import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
|
||||||
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
||||||
@@ -9,7 +10,7 @@ import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
|||||||
|
|
||||||
///
|
///
|
||||||
/// Form field allowing to select labels (i.e. correspondent, documentType)
|
/// Form field allowing to select labels (i.e. correspondent, documentType)
|
||||||
/// [T] is the label (model) type, [R] is the return type.
|
/// [T] is the label type (e.g. [DocumentType], [Correspondent], ...), [R] is the return type (e.g. [CorrespondentQuery], ...).
|
||||||
///
|
///
|
||||||
class LabelFormField<T extends Label, R extends IdQueryParameter> extends StatefulWidget {
|
class LabelFormField<T extends Label, R extends IdQueryParameter> extends StatefulWidget {
|
||||||
final Widget prefixIcon;
|
final Widget prefixIcon;
|
||||||
@@ -23,6 +24,7 @@ class LabelFormField<T extends Label, R extends IdQueryParameter> extends Statef
|
|||||||
final R Function() queryParameterNotAssignedBuilder;
|
final R Function() queryParameterNotAssignedBuilder;
|
||||||
final R Function(int? id) queryParameterIdBuilder;
|
final R Function(int? id) queryParameterIdBuilder;
|
||||||
final bool notAssignedSelectable;
|
final bool notAssignedSelectable;
|
||||||
|
final void Function(R?)? onChanged;
|
||||||
|
|
||||||
const LabelFormField({
|
const LabelFormField({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -37,6 +39,7 @@ class LabelFormField<T extends Label, R extends IdQueryParameter> extends Statef
|
|||||||
required this.formBuilderState,
|
required this.formBuilderState,
|
||||||
required this.prefixIcon,
|
required this.prefixIcon,
|
||||||
this.notAssignedSelectable = true,
|
this.notAssignedSelectable = true,
|
||||||
|
this.onChanged,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -71,6 +74,14 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FormBuilderTypeAhead<IdQueryParameter>(
|
return FormBuilderTypeAhead<IdQueryParameter>(
|
||||||
|
noItemsFoundBuilder: (context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Text(
|
||||||
|
S.of(context).labelFormFieldNoItemsFoundText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null),
|
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null),
|
||||||
name: widget.name,
|
name: widget.name,
|
||||||
itemBuilder: (context, suggestion) => ListTile(
|
itemBuilder: (context, suggestion) => ListTile(
|
||||||
@@ -90,6 +101,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
|||||||
},
|
},
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() => _showClearSuffixIcon = value?.isSet ?? false);
|
setState(() => _showClearSuffixIcon = value?.isSet ?? false);
|
||||||
|
widget.onChanged?.call(value as R);
|
||||||
},
|
},
|
||||||
controller: _textEditingController,
|
controller: _textEditingController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|||||||
@@ -64,16 +64,17 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
|||||||
child: LinearProgressIndicator(), preferredSize: Size.fromHeight(4.0))
|
child: LinearProgressIndicator(), preferredSize: Size.fromHeight(4.0))
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: Visibility(
|
||||||
|
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
onPressed: _onSubmit,
|
onPressed: _onSubmit,
|
||||||
label: Text(S.of(context).genericActionUploadLabel),
|
label: Text(S.of(context).genericActionUploadLabel),
|
||||||
icon: const Icon(Icons.upload),
|
icon: const Icon(Icons.upload),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
),
|
||||||
child: FormBuilder(
|
body: FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: ListView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
@@ -167,7 +168,6 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
|||||||
].padded(),
|
].padded(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,5 +167,6 @@
|
|||||||
"errorMessageLoadSavedViewsError": "Gespeicherte Ansichten konnten nicht geladen werden.",
|
"errorMessageLoadSavedViewsError": "Gespeicherte Ansichten konnten nicht geladen werden.",
|
||||||
"errorMessageCreateSavedViewError": "Gespeicherte Ansicht konnte nicht erstellt werden, bitte versuche es erneut.",
|
"errorMessageCreateSavedViewError": "Gespeicherte Ansicht konnte nicht erstellt werden, bitte versuche es erneut.",
|
||||||
"errorMessageDeleteSavedViewError": "Gespeicherte Ansicht konnte nicht geklöscht werden, bitte versuche es erneut.",
|
"errorMessageDeleteSavedViewError": "Gespeicherte Ansicht konnte nicht geklöscht werden, bitte versuche es erneut.",
|
||||||
"errorMessageRequestTimedOut": "Bei der Anfrage an den Server kam es zu einer Zeitüberschreitung."
|
"errorMessageRequestTimedOut": "Bei der Anfrage an den Server kam es zu einer Zeitüberschreitung.",
|
||||||
|
"labelFormFieldNoItemsFoundText": "Keine Treffer gefunden!"
|
||||||
}
|
}
|
||||||
@@ -168,5 +168,6 @@
|
|||||||
"errorMessageLoadSavedViewsError": "Could not load saved views.",
|
"errorMessageLoadSavedViewsError": "Could not load saved views.",
|
||||||
"errorMessageCreateSavedViewError": "Could not create saved view, please try again.",
|
"errorMessageCreateSavedViewError": "Could not create saved view, please try again.",
|
||||||
"errorMessageDeleteSavedViewError": "Could not delete saved view, please try again",
|
"errorMessageDeleteSavedViewError": "Could not delete saved view, please try again",
|
||||||
"errorMessageRequestTimedOut": "The request to the server timed out."
|
"errorMessageRequestTimedOut": "The request to the server timed out.",
|
||||||
|
"labelFormFieldNoItemsFoundText": "No items found!"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user