mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 12:07:58 -06:00
Improved search, changed saved view display
This commit is contained in:
232
lib/features/documents/view/widgets/adaptive_documents_view.dart
Normal file
232
lib/features/documents/view/widgets/adaptive_documents_view.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
abstract class AdaptiveDocumentsView extends StatelessWidget {
|
||||
final List<DocumentModel> documents;
|
||||
final bool isLoading;
|
||||
final bool hasLoaded;
|
||||
final bool enableHeroAnimation;
|
||||
final List<int> selectedDocumentIds;
|
||||
final ViewType viewType;
|
||||
final void Function(DocumentModel)? onTap;
|
||||
final void Function(DocumentModel)? onSelected;
|
||||
final bool hasInternetConnection;
|
||||
final bool isLabelClickable;
|
||||
final void Function(int id)? onTagSelected;
|
||||
final void Function(int? id)? onCorrespondentSelected;
|
||||
final void Function(int? id)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
const AdaptiveDocumentsView({
|
||||
super.key,
|
||||
this.selectedDocumentIds = const [],
|
||||
required this.documents,
|
||||
this.onTap,
|
||||
this.onSelected,
|
||||
this.viewType = ViewType.list,
|
||||
required this.hasInternetConnection,
|
||||
required this.isLabelClickable,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
required this.isLoading,
|
||||
required this.hasLoaded,
|
||||
this.enableHeroAnimation = true,
|
||||
});
|
||||
}
|
||||
|
||||
class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
||||
const SliverAdaptiveDocumentsView({
|
||||
super.key,
|
||||
required super.documents,
|
||||
required super.hasInternetConnection,
|
||||
required super.isLabelClickable,
|
||||
super.onCorrespondentSelected,
|
||||
super.onDocumentTypeSelected,
|
||||
super.onStoragePathSelected,
|
||||
super.onSelected,
|
||||
super.onTagSelected,
|
||||
super.onTap,
|
||||
super.selectedDocumentIds,
|
||||
super.viewType,
|
||||
required super.isLoading,
|
||||
required super.hasLoaded,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (viewType) {
|
||||
case ViewType.grid:
|
||||
return _buildGridView();
|
||||
case ViewType.list:
|
||||
return _buildListView();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildListView() {
|
||||
if (!hasLoaded && isLoading) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: documents.length,
|
||||
(context, index) {
|
||||
final document = documents.elementAt(index);
|
||||
return LabelRepositoriesProvider(
|
||||
child: DocumentListItem(
|
||||
isLabelClickable: isLabelClickable,
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: selectedDocumentIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView() {
|
||||
if (!hasLoaded && isLoading) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
return SliverGrid.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: 1 / 2,
|
||||
),
|
||||
itemCount: documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
final document = documents.elementAt(index);
|
||||
return DocumentGridItem(
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: selectedDocumentIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||
isLabelClickable: isLabelClickable,
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
enableHeroAnimation: enableHeroAnimation,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
||||
final ScrollController? scrollController;
|
||||
const DefaultAdaptiveDocumentsView({
|
||||
super.key,
|
||||
required super.documents,
|
||||
required super.hasInternetConnection,
|
||||
required super.isLabelClickable,
|
||||
required super.isLoading,
|
||||
required super.hasLoaded,
|
||||
super.onCorrespondentSelected,
|
||||
super.onDocumentTypeSelected,
|
||||
super.onStoragePathSelected,
|
||||
super.onSelected,
|
||||
super.onTagSelected,
|
||||
super.onTap,
|
||||
this.scrollController,
|
||||
super.selectedDocumentIds,
|
||||
super.viewType,
|
||||
super.enableHeroAnimation = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (viewType) {
|
||||
case ViewType.grid:
|
||||
return _buildGridView();
|
||||
case ViewType.list:
|
||||
return _buildListView();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildListView() {
|
||||
if (!hasLoaded && isLoading) {
|
||||
return const CustomScrollView(slivers: [
|
||||
DocumentsListLoadingWidget(),
|
||||
]);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
controller: scrollController,
|
||||
primary: false,
|
||||
itemCount: documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
final document = documents.elementAt(index);
|
||||
return LabelRepositoriesProvider(
|
||||
child: DocumentListItem(
|
||||
isLabelClickable: isLabelClickable,
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: selectedDocumentIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
enableHeroAnimation: enableHeroAnimation,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView() {
|
||||
if (!hasLoaded && isLoading) {
|
||||
return const CustomScrollView(
|
||||
slivers: [
|
||||
DocumentsListLoadingWidget(),
|
||||
],
|
||||
); //TODO: Build grid skeleton
|
||||
}
|
||||
return GridView.builder(
|
||||
controller: scrollController,
|
||||
primary: false,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: 1 / 2,
|
||||
),
|
||||
itemCount: documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
final document = documents.elementAt(index);
|
||||
return DocumentGridItem(
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: selectedDocumentIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||
isLabelClickable: isLabelClickable,
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
enableHeroAnimation: enableHeroAnimation,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class DocumentsEmptyState extends StatelessWidget {
|
||||
final PagedDocumentsState state;
|
||||
final VoidCallback onReset;
|
||||
final VoidCallback? onReset;
|
||||
const DocumentsEmptyState({
|
||||
Key? key,
|
||||
required this.state,
|
||||
required this.onReset,
|
||||
this.onReset,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -20,7 +20,7 @@ class DocumentsEmptyState extends StatelessWidget {
|
||||
child: EmptyState(
|
||||
title: S.of(context).documentsPageEmptyStateOopsText,
|
||||
subtitle: S.of(context).documentsPageEmptyStateNothingHereText,
|
||||
bottomChild: state.filter != DocumentFilter.initial
|
||||
bottomChild: state.filter != DocumentFilter.initial && onReset != null
|
||||
? TextButton(
|
||||
onPressed: onReset,
|
||||
child: Text(
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class DocumentsListLoadingWidget extends StatelessWidget {
|
||||
static const _tags = [" ", " ", " "];
|
||||
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
|
||||
static const _correspondentLengths = <double>[200.0, 300.0, 150.0];
|
||||
static const _fontSize = 16.0;
|
||||
|
||||
const DocumentsListLoadingWidget({super.key
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _random = Random();
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return _buildFakeListItem(context, _random);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFakeListItem(BuildContext context, Random random) {
|
||||
final tagCount = random.nextInt(_tags.length + 1);
|
||||
final correspondentLength =
|
||||
_correspondentLengths[random.nextInt(_correspondentLengths.length - 1)];
|
||||
final titleLength = _titleLengths[random.nextInt(_titleLengths.length - 1)];
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[300]!
|
||||
: Colors.grey[900]!,
|
||||
highlightColor: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[100]!
|
||||
: Colors.grey[600]!,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
dense: true,
|
||||
isThreeLine: true,
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
height: 50,
|
||||
width: 35,
|
||||
),
|
||||
),
|
||||
title: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
width: correspondentLength,
|
||||
height: _fontSize,
|
||||
color: Colors.white,
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
height: _fontSize,
|
||||
width: titleLength,
|
||||
color: Colors.white,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 2.0,
|
||||
children: List.generate(
|
||||
tagCount,
|
||||
(index) => InputChip(
|
||||
label: Text(_tags[random.nextInt(_tags.length)]),
|
||||
),
|
||||
),
|
||||
).paddedOnly(top: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DocumentGridItem extends StatelessWidget {
|
||||
final DocumentModel document;
|
||||
final bool isSelected;
|
||||
final void Function(DocumentModel) onTap;
|
||||
final void Function(DocumentModel) onSelected;
|
||||
final bool isAtLeastOneSelected;
|
||||
final bool Function(int tagId) isTagSelectedPredicate;
|
||||
final void Function(int tagId)? onTagSelected;
|
||||
|
||||
class DocumentGridItem extends DocumentItem {
|
||||
const DocumentGridItem({
|
||||
Key? key,
|
||||
required this.document,
|
||||
required this.onTap,
|
||||
required this.onSelected,
|
||||
required this.isSelected,
|
||||
required this.isAtLeastOneSelected,
|
||||
required this.isTagSelectedPredicate,
|
||||
required this.onTagSelected,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
required super.document,
|
||||
required super.isSelected,
|
||||
required super.isSelectionActive,
|
||||
required super.isLabelClickable,
|
||||
super.onCorrespondentSelected,
|
||||
super.onDocumentTypeSelected,
|
||||
super.onSelected,
|
||||
super.onStoragePathSelected,
|
||||
super.onTagSelected,
|
||||
super.onTap,
|
||||
required super.enableHeroAnimation,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
onLongPress: () => onSelected(document),
|
||||
onLongPress: onSelected != null ? () => onSelected!(document) : null,
|
||||
child: AbsorbPointer(
|
||||
absorbing: isAtLeastOneSelected,
|
||||
absorbing: isSelectionActive,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
@@ -48,6 +45,7 @@ class DocumentGridItem extends StatelessWidget {
|
||||
child: DocumentPreview(
|
||||
id: document.id,
|
||||
borderRadius: 12.0,
|
||||
enableHero: enableHeroAnimation,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -94,10 +92,10 @@ class DocumentGridItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
if (isAtLeastOneSelected || isSelected) {
|
||||
onSelected(document);
|
||||
if (isSelectionActive || isSelected) {
|
||||
onSelected?.call(document);
|
||||
} else {
|
||||
onTap(document);
|
||||
onTap?.call(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
lib/features/documents/view/widgets/items/document_item.dart
Normal file
32
lib/features/documents/view/widgets/items/document_item.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
abstract class DocumentItem extends StatelessWidget {
|
||||
final DocumentModel document;
|
||||
final void Function(DocumentModel)? onTap;
|
||||
final void Function(DocumentModel)? onSelected;
|
||||
final bool isSelected;
|
||||
final bool isSelectionActive;
|
||||
final bool isLabelClickable;
|
||||
final bool enableHeroAnimation;
|
||||
|
||||
final void Function(int tagId)? onTagSelected;
|
||||
final void Function(int? correspondentId)? onCorrespondentSelected;
|
||||
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
const DocumentItem({
|
||||
super.key,
|
||||
required this.document,
|
||||
this.onTap,
|
||||
this.onSelected,
|
||||
required this.isSelected,
|
||||
required this.isSelectionActive,
|
||||
required this.isLabelClickable,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
required this.enableHeroAnimation,
|
||||
});
|
||||
}
|
||||
@@ -1,39 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
|
||||
class DocumentListItem extends StatelessWidget {
|
||||
class DocumentListItem extends DocumentItem {
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
final DocumentModel document;
|
||||
final void Function(DocumentModel)? onTap;
|
||||
final void Function(DocumentModel)? onSelected;
|
||||
final bool isSelected;
|
||||
final bool isAtLeastOneSelected;
|
||||
final bool isLabelClickable;
|
||||
|
||||
final void Function(int tagId)? onTagSelected;
|
||||
final void Function(int? correspondentId)? onCorrespondentSelected;
|
||||
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
final bool enableHeroAnimation;
|
||||
|
||||
const DocumentListItem({
|
||||
Key? key,
|
||||
required this.document,
|
||||
this.onTap,
|
||||
this.onSelected,
|
||||
this.isSelected = false,
|
||||
this.isAtLeastOneSelected = false,
|
||||
this.isLabelClickable = true,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
this.enableHeroAnimation = true,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
required super.document,
|
||||
required super.isSelected,
|
||||
required super.isSelectionActive,
|
||||
required super.isLabelClickable,
|
||||
super.onCorrespondentSelected,
|
||||
super.onDocumentTypeSelected,
|
||||
super.onSelected,
|
||||
super.onStoragePathSelected,
|
||||
super.onTagSelected,
|
||||
super.onTap,
|
||||
super.enableHeroAnimation = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -50,7 +37,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: isAtLeastOneSelected,
|
||||
absorbing: isSelectionActive,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
@@ -69,7 +56,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: AbsorbPointer(
|
||||
absorbing: isAtLeastOneSelected,
|
||||
absorbing: isSelectionActive,
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tagIds: document.tags,
|
||||
@@ -95,7 +82,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
if (isAtLeastOneSelected || isSelected) {
|
||||
if (isSelectionActive || isSelected) {
|
||||
onSelected?.call(document);
|
||||
} else {
|
||||
onTap?.call(document);
|
||||
@@ -1,114 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid_item.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
class AdaptiveDocumentsView extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
final ViewType viewType;
|
||||
final Widget? beforeItems;
|
||||
final void Function(DocumentModel) onTap;
|
||||
final void Function(DocumentModel) onSelected;
|
||||
final ScrollController scrollController;
|
||||
final bool hasInternetConnection;
|
||||
final bool isLabelClickable;
|
||||
final void Function(int id)? onTagSelected;
|
||||
final void Function(int? id)? onCorrespondentSelected;
|
||||
final void Function(int? id)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
final Widget pageLoadingWidget;
|
||||
|
||||
const AdaptiveDocumentsView({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
required this.scrollController,
|
||||
required this.state,
|
||||
required this.onSelected,
|
||||
required this.hasInternetConnection,
|
||||
this.isLabelClickable = true,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
required this.pageLoadingWidget,
|
||||
this.beforeItems,
|
||||
required this.viewType,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomScrollView(
|
||||
controller: scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: beforeItems),
|
||||
if (viewType == ViewType.list) _buildListView() else _buildGridView(),
|
||||
if (state.hasLoaded && state.isLoading)
|
||||
SliverToBoxAdapter(child: pageLoadingWidget),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
SliverList _buildListView() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: state.documents.length,
|
||||
(context, index) {
|
||||
final document = state.documents.elementAt(index);
|
||||
return LabelRepositoriesProvider(
|
||||
child: DocumentListItem(
|
||||
isLabelClickable: isLabelClickable,
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: state.selectedIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isAtLeastOneSelected: state.selection.isNotEmpty,
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView() {
|
||||
return SliverGrid.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: 1 / 2,
|
||||
),
|
||||
itemCount: state.documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (state.hasLoaded &&
|
||||
state.isLoading &&
|
||||
index == state.documents.length) {
|
||||
return Center(child: pageLoadingWidget);
|
||||
}
|
||||
final document = state.documents.elementAt(index);
|
||||
return DocumentGridItem(
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: state.selectedIds.contains(document.id),
|
||||
onSelected: onSelected,
|
||||
isAtLeastOneSelected: state.selection.isNotEmpty,
|
||||
isTagSelectedPredicate: (int tagId) {
|
||||
return state.filter.tags is IdsTagsQuery
|
||||
? (state.filter.tags as IdsTagsQuery)
|
||||
.includedIds
|
||||
.contains(tagId)
|
||||
: false;
|
||||
},
|
||||
onTagSelected: onTagSelected,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
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/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.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/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 'text_query_form_field.dart';
|
||||
|
||||
class DocumentFilterForm extends StatefulWidget {
|
||||
static const fkCorrespondent = DocumentModel.correspondentKey;
|
||||
static const fkDocumentType = DocumentModel.documentTypeKey;
|
||||
static const fkStoragePath = DocumentModel.storagePathKey;
|
||||
static const fkQuery = "query";
|
||||
static const fkCreatedAt = DocumentModel.createdKey;
|
||||
static const fkAddedAt = DocumentModel.addedKey;
|
||||
|
||||
static DocumentFilter assembleFilter(
|
||||
GlobalKey<FormBuilderState> formKey, DocumentFilter initialFilter) {
|
||||
formKey.currentState?.save();
|
||||
final v = formKey.currentState!.value;
|
||||
return DocumentFilter(
|
||||
correspondent:
|
||||
v[DocumentFilterForm.fkCorrespondent] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
documentType: v[DocumentFilterForm.fkDocumentType] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.documentType,
|
||||
storagePath: v[DocumentFilterForm.fkStoragePath] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.storagePath,
|
||||
tags:
|
||||
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||
query: v[DocumentFilterForm.fkQuery] as TextQuery? ??
|
||||
DocumentFilter.initial.query,
|
||||
created: (v[DocumentFilterForm.fkCreatedAt] as DateRangeQuery),
|
||||
added: (v[DocumentFilterForm.fkAddedAt] as DateRangeQuery),
|
||||
asnQuery: initialFilter.asnQuery,
|
||||
page: 1,
|
||||
pageSize: initialFilter.pageSize,
|
||||
sortField: initialFilter.sortField,
|
||||
sortOrder: initialFilter.sortOrder,
|
||||
);
|
||||
}
|
||||
|
||||
final Widget? header;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
final DocumentFilter initialFilter;
|
||||
final ScrollController? scrollController;
|
||||
final EdgeInsets padding;
|
||||
const DocumentFilterForm({
|
||||
super.key,
|
||||
this.header,
|
||||
required this.formKey,
|
||||
required this.initialFilter,
|
||||
this.scrollController,
|
||||
this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
});
|
||||
|
||||
@override
|
||||
State<DocumentFilterForm> createState() => _DocumentFilterFormState();
|
||||
}
|
||||
|
||||
class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
late bool _allowOnlyExtendedQuery;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_allowOnlyExtendedQuery = widget.initialFilter.forceExtendedQuery;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormBuilder(
|
||||
key: widget.formKey,
|
||||
child: CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
if (widget.header != null) widget.header!,
|
||||
..._buildFormFieldList(),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildFormFieldList() {
|
||||
return [
|
||||
_buildQueryFormField(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterAdvancedLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentFilterForm.fkCreatedAt,
|
||||
initialValue: widget.initialFilter.created,
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
onChanged: (_) {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentFilterForm.fkAddedAt,
|
||||
initialValue: widget.initialFilter.added,
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
onChanged: (_) {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
),
|
||||
_buildCorrespondentFormField(),
|
||||
_buildDocumentTypeFormField(),
|
||||
_buildStoragePathFormField(),
|
||||
_buildTagsFormField(),
|
||||
]
|
||||
.map((w) => SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: SliverToBoxAdapter(child: w),
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _checkQueryConstraints() {
|
||||
final filter =
|
||||
DocumentFilterForm.assembleFilter(widget.formKey, widget.initialFilter);
|
||||
if (filter.forceExtendedQuery) {
|
||||
setState(() => _allowOnlyExtendedQuery = true);
|
||||
final queryField =
|
||||
widget.formKey.currentState?.fields[DocumentFilterForm.fkQuery];
|
||||
queryField?.didChange(
|
||||
(queryField.value as TextQuery?)
|
||||
?.copyWith(queryType: QueryType.extended),
|
||||
);
|
||||
} else {
|
||||
setState(() => _allowOnlyExtendedQuery = false);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType>(
|
||||
formBuilderState: widget.formKey.currentState,
|
||||
name: DocumentFilterForm.fkDocumentType,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField() {
|
||||
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent>(
|
||||
formBuilderState: widget.formKey.currentState,
|
||||
name: DocumentFilterForm.fkCorrespondent,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField() {
|
||||
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath>(
|
||||
formBuilderState: widget.formKey.currentState,
|
||||
name: DocumentFilterForm.fkStoragePath,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQueryFormField() {
|
||||
return TextQueryFormField(
|
||||
name: DocumentFilterForm.fkQuery,
|
||||
onlyExtendedQueryAllowed: _allowOnlyExtendedQuery,
|
||||
initialValue: widget.initialFilter.query,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/text_query_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
@@ -32,22 +33,14 @@ class DocumentFilterPanel extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
static const fkCorrespondent = DocumentModel.correspondentKey;
|
||||
static const fkDocumentType = DocumentModel.documentTypeKey;
|
||||
static const fkStoragePath = DocumentModel.storagePathKey;
|
||||
static const fkQuery = "query";
|
||||
static const fkCreatedAt = DocumentModel.createdKey;
|
||||
static const fkAddedAt = DocumentModel.addedKey;
|
||||
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
late bool _allowOnlyExtendedQuery;
|
||||
|
||||
double _heightAnimationValue = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_allowOnlyExtendedQuery = widget.initialFilter.forceExtendedQuery;
|
||||
|
||||
widget.draggableSheetController.addListener(animateTitleByDrag);
|
||||
}
|
||||
|
||||
@@ -106,100 +99,59 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: _buildFormList(context),
|
||||
body: DocumentFilterForm(
|
||||
formKey: _formKey,
|
||||
scrollController: widget.scrollController,
|
||||
initialFilter: widget.initialFilter,
|
||||
header: _buildPanelHeader(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFormList(BuildContext context) {
|
||||
return CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
toolbarHeight: kToolbarHeight + 22,
|
||||
title: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 1 - _heightAnimationValue,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 11),
|
||||
child: _buildDragHandle(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: max(0, (_heightAnimationValue - 0.5) * 2),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: const Icon(Icons.expand_more_rounded),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(left: _heightAnimationValue * 48),
|
||||
child: Text(S.of(context).documentFilterTitle),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
Widget _buildPanelHeader() {
|
||||
return SliverAppBar(
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
toolbarHeight: kToolbarHeight + 22,
|
||||
title: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 1 - _heightAnimationValue,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 11),
|
||||
child: _buildDragHandle(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: max(0, (_heightAnimationValue - 0.5) * 2),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: const Icon(Icons.expand_more_rounded),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: _heightAnimationValue * 48),
|
||||
child: Text(S.of(context).documentFilterTitle),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
..._buildFormFieldList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildFormFieldList() {
|
||||
return [
|
||||
_buildQueryFormField().paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterAdvancedLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: fkCreatedAt,
|
||||
initialValue: widget.initialFilter.created,
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
onChanged: (_) {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
).paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: fkAddedAt,
|
||||
initialValue: widget.initialFilter.added,
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
onChanged: (_) {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
).paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
_buildCorrespondentFormField()
|
||||
.paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
_buildDocumentTypeFormField()
|
||||
.paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
_buildStoragePathFormField()
|
||||
.paddedSymmetrically(vertical: 8, horizontal: 16),
|
||||
_buildTagsFormField().padded(16),
|
||||
].map((w) => SliverToBoxAdapter(child: w)).toList();
|
||||
}
|
||||
|
||||
Container _buildDragHandle() {
|
||||
return Container(
|
||||
// According to m3 spec https://m3.material.io/components/bottom-sheets/specs
|
||||
@@ -212,19 +164,6 @@ 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _resetFilter() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(
|
||||
@@ -233,102 +172,13 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkDocumentType,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField() {
|
||||
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkCorrespondent,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField() {
|
||||
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkStoragePath,
|
||||
labelOptions: state.labels,
|
||||
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQueryFormField() {
|
||||
return TextQueryFormField(
|
||||
name: fkQuery,
|
||||
onlyExtendedQueryAllowed: _allowOnlyExtendedQuery,
|
||||
initialValue: widget.initialFilter.query,
|
||||
);
|
||||
}
|
||||
|
||||
void _onApplyFilter() async {
|
||||
_formKey.currentState?.save();
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
DocumentFilter newFilter = _assembleFilter();
|
||||
DocumentFilter newFilter =
|
||||
DocumentFilterForm.assembleFilter(_formKey, widget.initialFilter);
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(context, DocumentFilterIntent(filter: newFilter));
|
||||
}
|
||||
}
|
||||
|
||||
DocumentFilter _assembleFilter() {
|
||||
_formKey.currentState?.save();
|
||||
final v = _formKey.currentState!.value;
|
||||
return DocumentFilter(
|
||||
correspondent: v[fkCorrespondent] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
documentType: v[fkDocumentType] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.documentType,
|
||||
storagePath: v[fkStoragePath] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.storagePath,
|
||||
tags:
|
||||
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||
query: v[fkQuery] as TextQuery? ?? DocumentFilter.initial.query,
|
||||
created: (v[fkCreatedAt] as DateRangeQuery),
|
||||
added: (v[fkAddedAt] as DateRangeQuery),
|
||||
asnQuery: widget.initialFilter.asnQuery,
|
||||
page: 1,
|
||||
pageSize: widget.initialFilter.pageSize,
|
||||
sortField: widget.initialFilter.sortField,
|
||||
sortOrder: widget.initialFilter.sortOrder,
|
||||
);
|
||||
}
|
||||
|
||||
void _checkQueryConstraints() {
|
||||
final filter = _assembleFilter();
|
||||
if (filter.forceExtendedQuery) {
|
||||
setState(() => _allowOnlyExtendedQuery = true);
|
||||
final queryField = _formKey.currentState?.fields[fkQuery];
|
||||
queryField?.didChange(
|
||||
(queryField.value as TextQuery?)
|
||||
?.copyWith(queryType: QueryType.extended),
|
||||
);
|
||||
} else {
|
||||
setState(() => _allowOnlyExtendedQuery = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
lib/features/documents/view/widgets/view_actions.dart
Normal file
39
lib/features/documents/view/widgets/view_actions.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
class ViewActions extends StatelessWidget {
|
||||
const ViewActions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SortDocumentsButton(),
|
||||
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
builder: (context, settings) {
|
||||
final cubit = context.read<ApplicationSettingsCubit>();
|
||||
switch (settings.preferredViewType) {
|
||||
case ViewType.grid:
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.list),
|
||||
onPressed: () =>
|
||||
cubit.setViewType(settings.preferredViewType.toggle()),
|
||||
);
|
||||
case ViewType.list:
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.grid_view_rounded),
|
||||
onPressed: () =>
|
||||
cubit.setViewType(settings.preferredViewType.toggle()),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user