mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 12:08:05 -06:00
Updated onboarding, reformatted files, improved referenced documents view, updated error handling
This commit is contained in:
@@ -32,9 +32,14 @@ import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class DocumentDetailsPage extends StatefulWidget {
|
||||
final int documentId;
|
||||
final bool allowEdit;
|
||||
final bool isLabelClickable;
|
||||
|
||||
const DocumentDetailsPage({
|
||||
Key? key,
|
||||
required this.documentId,
|
||||
this.allowEdit = true,
|
||||
this.isLabelClickable = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -42,7 +47,8 @@ class DocumentDetailsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
static final DateFormat _detailedDateFormat = DateFormat("MMM d, yyyy HH:mm:ss");
|
||||
static final DateFormat _detailedDateFormat =
|
||||
DateFormat("MMM d, yyyy HH:mm:ss");
|
||||
|
||||
bool _isDownloadPending = false;
|
||||
bool _isAssignAsnPending = false;
|
||||
@@ -52,98 +58,112 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
// buildWhen required because rebuild would happen after delete causing error.
|
||||
buildWhen: (previous, current) {
|
||||
return current.documents.where((element) => element.id == widget.documentId).isNotEmpty;
|
||||
return current.documents
|
||||
.where((element) => element.id == widget.documentId)
|
||||
.isNotEmpty;
|
||||
},
|
||||
builder: (context, state) {
|
||||
final document = state.documents.where((doc) => doc.id == widget.documentId).first;
|
||||
return SafeArea(
|
||||
bottom: true,
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () => _onEdit(document),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => _onDelete(document),
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.download),
|
||||
onPressed: Platform.isAndroid ? () => _onDownload(document) : null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
onPressed: () => _onOpen(document),
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onPressed: () => _onShare(document),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverAppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors
|
||||
.black, //TODO: check if there is a way to dynamically determine color...
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
floating: true,
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
flexibleSpace: DocumentPreview(
|
||||
id: document.id,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
bottom: ColoredTabBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
tabBar: TabBar(
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabOverviewLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabContentLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabMetaDataLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
final document =
|
||||
state.documents.where((doc) => doc.id == widget.documentId).first;
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: widget.allowEdit
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () => _onEdit(document),
|
||||
)
|
||||
: null,
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed:
|
||||
widget.allowEdit ? () => _onDelete(document) : null,
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 4)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.download),
|
||||
onPressed:
|
||||
Platform.isAndroid ? () => _onDownload(document) : null,
|
||||
).padded(const EdgeInsets.only(right: 4)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
onPressed: () => _onOpen(document),
|
||||
).padded(const EdgeInsets.only(right: 4)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onPressed: () => _onShare(document),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
children: [
|
||||
_buildDocumentOverview(document, state.filter.titleAndContentMatchString),
|
||||
_buildDocumentContentView(document, state.filter.titleAndContentMatchString),
|
||||
_buildDocumentMetaDataView(document),
|
||||
].padded(),
|
||||
),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverAppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors
|
||||
.black, //TODO: check if there is a way to dynamically determine color...
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
floating: true,
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
flexibleSpace: DocumentPreview(
|
||||
id: document.id,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
bottom: ColoredTabBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primaryContainer,
|
||||
tabBar: TabBar(
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabOverviewLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabContentLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
S.of(context).documentDetailsPageTabMetaDataLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
children: [
|
||||
_buildDocumentOverview(
|
||||
document, state.filter.titleAndContentMatchString),
|
||||
_buildDocumentContentView(
|
||||
document, state.filter.titleAndContentMatchString),
|
||||
_buildDocumentMetaDataView(document),
|
||||
].padded(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -163,18 +183,25 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return ListView(
|
||||
children: [
|
||||
_DetailsItem.text(_detailedDateFormat.format(document.modified),
|
||||
label: S.of(context).documentModifiedPropertyLabel, context: context),
|
||||
label: S.of(context).documentModifiedPropertyLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
_DetailsItem.text(_detailedDateFormat.format(document.added),
|
||||
label: S.of(context).documentAddedPropertyLabel, context: context),
|
||||
label: S.of(context).documentAddedPropertyLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
label: S.of(context).documentArchiveSerialNumberPropertyLongLabel,
|
||||
content: document.archiveSerialNumber != null
|
||||
? Text(document.archiveSerialNumber.toString())
|
||||
: OutlinedButton(
|
||||
child: Text(S.of(context).documentDetailsPageAssignAsnButtonLabel),
|
||||
onPressed: () => BlocProvider.of<DocumentsCubit>(context).assignAsn(document),
|
||||
child: Text(S
|
||||
.of(context)
|
||||
.documentDetailsPageAssignAsnButtonLabel),
|
||||
onPressed: widget.allowEdit
|
||||
? () => BlocProvider.of<DocumentsCubit>(context)
|
||||
.assignAsn(document)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
_separator(),
|
||||
@@ -191,7 +218,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
_separator(),
|
||||
_DetailsItem.text(formatBytes(meta.originalSize, 2),
|
||||
label: S.of(context).documentMetaDataOriginalFileSizeLabel, context: context),
|
||||
label: S.of(context).documentMetaDataOriginalFileSizeLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
_DetailsItem.text(
|
||||
meta.originalMimeType,
|
||||
@@ -239,6 +267,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
content: DocumentTypeWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
documentTypeId: document.documentType,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
@@ -250,6 +279,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
_DetailsItem(
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
content: CorrespondentWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
@@ -260,6 +290,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
_DetailsItem(
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
content: StoragePathWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
pathId: document.storagePath,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
@@ -272,6 +303,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: TagsWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
tagIds: document.tags,
|
||||
),
|
||||
),
|
||||
@@ -321,13 +353,15 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Future<void> _onDownload(DocumentModel document) async {
|
||||
if (!Platform.isAndroid) {
|
||||
showSnackBar(context, "This feature is currently only supported on Android!");
|
||||
showSnackBar(
|
||||
context, "This feature is currently only supported on Android!");
|
||||
return;
|
||||
}
|
||||
setState(() => _isDownloadPending = true);
|
||||
getIt<DocumentRepository>().download(document).then((bytes) async {
|
||||
final Directory dir =
|
||||
(await getExternalStorageDirectories(type: StorageDirectory.downloads))!.first;
|
||||
final Directory dir = (await getExternalStorageDirectories(
|
||||
type: StorageDirectory.downloads))!
|
||||
.first;
|
||||
String filePath = "${dir.path}/${document.originalFileName}";
|
||||
//TODO: Add replacement mechanism here (ask user if file should be replaced if exists)
|
||||
await File(filePath).writeAsBytes(bytes);
|
||||
@@ -340,7 +374,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
/// Downloads file to temporary directory, from which it can then be shared.
|
||||
///
|
||||
Future<void> _onShare(DocumentModel document) async {
|
||||
Uint8List documentBytes = await getIt<DocumentRepository>().download(document);
|
||||
Uint8List documentBytes =
|
||||
await getIt<DocumentRepository>().download(document);
|
||||
final dir = await getTemporaryDirectory();
|
||||
final String path = "${dir.path}/${document.originalFileName}";
|
||||
await File(path).writeAsBytes(documentBytes);
|
||||
@@ -359,14 +394,16 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Future<void> _onDelete(DocumentModel document) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeleteDocumentConfirmationDialog(document: document)).then((delete) {
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
DeleteDocumentConfirmationDialog(document: document))
|
||||
.then((delete) {
|
||||
if (delete ?? false) {
|
||||
BlocProvider.of<DocumentsCubit>(context).removeDocument(document).then((value) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.removeDocument(document)
|
||||
.then((value) {
|
||||
Navigator.pop(context);
|
||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||
}).onError<ErrorMessage>((error, _) {
|
||||
showSnackBar(context, translateError(context, error.code));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -384,14 +421,17 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
if (bytes <= 0) return "0 B";
|
||||
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var i = (log(bytes) / log(1024)).floor();
|
||||
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i];
|
||||
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) +
|
||||
' ' +
|
||||
suffixes[i];
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailsItem extends StatelessWidget {
|
||||
final String label;
|
||||
final Widget content;
|
||||
const _DetailsItem({Key? key, required this.label, required this.content}) : super(key: key);
|
||||
const _DetailsItem({Key? key, required this.label, required this.content})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -402,7 +442,10 @@ class _DetailsItem extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.headline5?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline5
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
content,
|
||||
],
|
||||
|
||||
@@ -78,7 +78,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
});
|
||||
await getIt<DocumentsCubit>().updateDocument(updatedDocument);
|
||||
Navigator.pop(context);
|
||||
showSnackBar(context, "Document successfully updated."); //TODO: INTL
|
||||
showSnackBar(
|
||||
context, "Document successfully updated."); //TODO: INTL
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
@@ -111,18 +112,21 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) => BlocProvider.value(
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: DocumentTypeQuery.fromId(widget.document.documentType),
|
||||
initialValue:
|
||||
DocumentTypeQuery.fromId(widget.document.documentType),
|
||||
state: state,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
queryParameterNotAssignedBuilder:
|
||||
DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
@@ -132,16 +136,19 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => BlocProvider.value(
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context),
|
||||
child: AddCorrespondentPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: state,
|
||||
initialValue: CorrespondentQuery.fromId(widget.document.correspondent),
|
||||
initialValue:
|
||||
CorrespondentQuery.fromId(widget.document.correspondent),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
queryParameterNotAssignedBuilder:
|
||||
CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
},
|
||||
@@ -151,16 +158,19 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => BlocProvider.value(
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<StoragePathCubit>(context),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
state: state,
|
||||
initialValue: StoragePathQuery.fromId(widget.document.storagePath),
|
||||
initialValue:
|
||||
StoragePathQuery.fromId(widget.document.storagePath),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
queryParameterNotAssignedBuilder:
|
||||
StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -46,12 +46,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
super.initState();
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (!documentsCubit.state.isLoaded) {
|
||||
documentsCubit.loadDocuments().onError<ErrorMessage>(
|
||||
(error, stackTrace) => showSnackBar(
|
||||
context,
|
||||
translateError(context, error.code),
|
||||
),
|
||||
);
|
||||
documentsCubit.loadDocuments();
|
||||
}
|
||||
_pagingController.addPageRequestListener(_loadNewPage);
|
||||
}
|
||||
@@ -64,8 +59,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
final pageCount =
|
||||
documentsCubit.state.inferPageCount(pageSize: documentsCubit.state.filter.pageSize);
|
||||
final pageCount = documentsCubit.state
|
||||
.inferPageCount(pageSize: documentsCubit.state.filter.pageSize);
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
@@ -78,11 +73,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
|
||||
Future<void> _onRefresh() {
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
return documentsCubit
|
||||
.updateFilter(filter: documentsCubit.state.filter.copyWith(page: 1))
|
||||
.onError<ErrorMessage>((error, _) {
|
||||
showSnackBar(context, translateError(context, error.code));
|
||||
});
|
||||
return documentsCubit.updateFilter(
|
||||
filter: documentsCubit.state.filter.copyWith(page: 1));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -103,7 +95,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
},
|
||||
child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected && current == ConnectivityState.connected,
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
||||
},
|
||||
@@ -114,7 +107,6 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
child: const InfoDrawer(),
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: connectivityState == ConnectivityState.connected ? null : const OfflineBanner(),
|
||||
body: SlidingUpPanel(
|
||||
backdropEnabled: true,
|
||||
parallaxEnabled: true,
|
||||
@@ -122,7 +114,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
controller: _panelController,
|
||||
defaultPanelState: PanelState.CLOSED,
|
||||
minHeight: 48,
|
||||
maxHeight: MediaQuery.of(context).size.height - kBottomNavigationBarHeight,
|
||||
maxHeight: MediaQuery.of(context).size.height -
|
||||
kBottomNavigationBarHeight,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
@@ -157,7 +150,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
state: state,
|
||||
onSelected: _onSelected,
|
||||
pagingController: _pagingController,
|
||||
hasInternetConnection: connectivityState == ConnectivityState.connected,
|
||||
hasInternetConnection:
|
||||
connectivityState == ConnectivityState.connected,
|
||||
);
|
||||
break;
|
||||
case ViewType.grid:
|
||||
@@ -166,7 +160,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
state: state,
|
||||
onSelected: _onSelected,
|
||||
pagingController: _pagingController,
|
||||
hasInternetConnection: connectivityState == ConnectivityState.connected);
|
||||
hasInternetConnection:
|
||||
connectivityState == ConnectivityState.connected);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -191,9 +186,12 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
const SortDocumentsButton(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_viewType == ViewType.grid ? Icons.list : Icons.grid_view,
|
||||
_viewType == ViewType.grid
|
||||
? Icons.list
|
||||
: Icons.grid_view,
|
||||
),
|
||||
onPressed: () => setState(() => _viewType = _viewType.toggle()),
|
||||
onPressed: () =>
|
||||
setState(() => _viewType = _viewType.toggle()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -26,7 +26,8 @@ class DeleteDocumentConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
||||
Text(
|
||||
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -36,7 +37,8 @@ class DeleteDocumentConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(Theme.of(context).colorScheme.error),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
@@ -7,6 +9,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentsEmptyState extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
|
||||
@@ -51,8 +51,10 @@ class DocumentGridItem extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CorrespondentWidget(correspondentId: document.correspondent),
|
||||
DocumentTypeWidget(documentTypeId: document.documentType),
|
||||
CorrespondentWidget(
|
||||
correspondentId: document.correspondent),
|
||||
DocumentTypeWidget(
|
||||
documentTypeId: document.documentType),
|
||||
Text(
|
||||
document.title,
|
||||
maxLines: document.tags.isEmpty ? 3 : 2,
|
||||
@@ -64,7 +66,8 @@ class DocumentGridItem extends StatelessWidget {
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
),
|
||||
Text(DateFormat.yMMMd(Intl.getCurrentLocale()).format(document.created)),
|
||||
Text(DateFormat.yMMMd(Intl.getCurrentLocale())
|
||||
.format(document.created)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,12 +7,13 @@ import 'package:paperless_mobile/features/documents/view/widgets/list/document_l
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
class DocumentListView extends StatelessWidget {
|
||||
final void Function(DocumentModel model) onTap;
|
||||
final void Function(DocumentModel) onTap;
|
||||
final void Function(DocumentModel) onSelected;
|
||||
|
||||
final PagingController<int, DocumentModel> pagingController;
|
||||
final DocumentsState state;
|
||||
final bool hasInternetConnection;
|
||||
|
||||
final bool isLabelClickable;
|
||||
const DocumentListView({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
@@ -20,24 +21,28 @@ class DocumentListView extends StatelessWidget {
|
||||
required this.state,
|
||||
required this.onSelected,
|
||||
required this.hasInternetConnection,
|
||||
this.isLabelClickable = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PagedSliverList<int, DocumentModel>(
|
||||
pagingController: pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate(
|
||||
animateTransitions: true,
|
||||
itemBuilder: (context, item, index) {
|
||||
itemBuilder: (context, document, index) {
|
||||
return DocumentListItem(
|
||||
document: item,
|
||||
isLabelClickable: isLabelClickable,
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
isSelected: state.selection.contains(item),
|
||||
isSelected: state.selection.contains(document),
|
||||
onSelected: onSelected,
|
||||
isAtLeastOneSelected: state.selection.isNotEmpty,
|
||||
);
|
||||
},
|
||||
noItemsFoundIndicatorBuilder: (context) =>
|
||||
hasInternetConnection ? const DocumentsListLoadingWidget() : const OfflineWidget(),
|
||||
noItemsFoundIndicatorBuilder: (context) => hasInternetConnection
|
||||
? const DocumentsListLoadingWidget()
|
||||
: const OfflineWidget(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import 'package:paperless_mobile/features/labels/correspondent/view/widgets/corr
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
|
||||
class DocumentListItem extends StatelessWidget {
|
||||
static const a4AspectRatio = 1 / 1.4142;
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
final DocumentModel document;
|
||||
final bool isSelected;
|
||||
final void Function(DocumentModel) onTap;
|
||||
final void Function(DocumentModel)? onSelected;
|
||||
final bool isSelected;
|
||||
final bool isAtLeastOneSelected;
|
||||
final bool isLabelClickable;
|
||||
|
||||
const DocumentListItem({
|
||||
Key? key,
|
||||
@@ -19,6 +20,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
this.onSelected,
|
||||
required this.isSelected,
|
||||
required this.isAtLeastOneSelected,
|
||||
this.isLabelClickable = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -39,6 +41,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
AbsorbPointer(
|
||||
absorbing: isAtLeastOneSelected,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
afterSelected: () {},
|
||||
),
|
||||
@@ -57,6 +60,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
child: AbsorbPointer(
|
||||
absorbing: isAtLeastOneSelected,
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
),
|
||||
@@ -64,7 +68,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
),
|
||||
isThreeLine: document.tags.isNotEmpty,
|
||||
leading: AspectRatio(
|
||||
aspectRatio: a4AspectRatio,
|
||||
aspectRatio: _a4AspectRatio,
|
||||
child: GestureDetector(
|
||||
child: DocumentPreview(
|
||||
id: document.id,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.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/bloc/documents_state.dart';
|
||||
@@ -24,6 +26,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.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 }
|
||||
@@ -108,7 +111,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
@@ -126,7 +130,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _onApplyFilter,
|
||||
child: Text(S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
@@ -225,15 +230,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
}
|
||||
|
||||
Widget _buildQueryFormField(DocumentsState state) {
|
||||
final queryType = _formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ??
|
||||
QueryType.titleAndContent;
|
||||
final queryType =
|
||||
_formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ??
|
||||
QueryType.titleAndContent;
|
||||
late String label;
|
||||
switch (queryType) {
|
||||
case QueryType.title:
|
||||
label = S.of(context).documentsFilterPageQueryOptionsTitleLabel;
|
||||
break;
|
||||
case QueryType.titleAndContent:
|
||||
label = S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel;
|
||||
label =
|
||||
S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel;
|
||||
break;
|
||||
case QueryType.extended:
|
||||
label = S.of(context).documentsFilterPageQueryOptionsExtendedLabel;
|
||||
@@ -255,7 +262,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
).padded();
|
||||
}
|
||||
|
||||
Widget _buildDateRangePickerHelper(DocumentsState state, String formFieldKey) {
|
||||
Widget _buildDateRangePickerHelper(
|
||||
DocumentsState state, String formFieldKey) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
@@ -279,10 +287,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
onPressed: () {
|
||||
final now = DateTime.now();
|
||||
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -1);
|
||||
final firstDayOfLastMonth =
|
||||
DateUtils.addMonthsToMonthDate(now, -1);
|
||||
_formKey.currentState?.fields[formFieldKey]?.didChange(
|
||||
DateTimeRange(
|
||||
start: DateTime(firstDayOfLastMonth.year, firstDayOfLastMonth.month, now.day),
|
||||
start: DateTime(firstDayOfLastMonth.year,
|
||||
firstDayOfLastMonth.month, now.day),
|
||||
end: DateTime.now(),
|
||||
),
|
||||
);
|
||||
@@ -294,7 +304,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
onPressed: () {
|
||||
final now = DateTime.now();
|
||||
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -3);
|
||||
final firstDayOfLastMonth =
|
||||
DateUtils.addMonthsToMonthDate(now, -3);
|
||||
_formKey.currentState?.fields[formFieldKey]?.didChange(
|
||||
DateTimeRange(
|
||||
start: DateTime(
|
||||
@@ -313,7 +324,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
onPressed: () {
|
||||
final now = DateTime.now();
|
||||
final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -12);
|
||||
final firstDayOfLastMonth =
|
||||
DateUtils.addMonthsToMonthDate(now, -12);
|
||||
_formKey.currentState?.fields[formFieldKey]?.didChange(
|
||||
DateTimeRange(
|
||||
start: DateTime(
|
||||
@@ -345,7 +357,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
data: Theme.of(context).copyWith(
|
||||
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
|
||||
iconTheme:
|
||||
IconThemeData(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||
onPrimary: Theme.of(context).primaryColor,
|
||||
@@ -355,8 +368,10 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
child: child!,
|
||||
),
|
||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||
fieldStartLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
|
||||
lastDate: DateTime.now(),
|
||||
name: fkCreatedAt,
|
||||
@@ -365,7 +380,8 @@ 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -388,7 +404,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
data: Theme.of(context).copyWith(
|
||||
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||
iconTheme: IconThemeData(color: Theme.of(context).primaryColor),
|
||||
iconTheme:
|
||||
IconThemeData(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||
onPrimary: Theme.of(context).primaryColor,
|
||||
@@ -398,8 +415,10 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
child: child!,
|
||||
),
|
||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||
fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||
fieldStartLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldEndLabel,
|
||||
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
|
||||
lastDate: DateTime.now(),
|
||||
name: fkAddedAt,
|
||||
@@ -408,7 +427,8 @@ 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -444,16 +464,16 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
separatorBuilder: (context, index) => const SizedBox(
|
||||
width: 8.0,
|
||||
),
|
||||
itemBuilder: (context, index) =>
|
||||
_buildActionChip(_sortFields[index], state.filter.sortField, context),
|
||||
itemBuilder: (context, index) => _buildActionChip(
|
||||
_sortFields[index], state.filter.sortField, context),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded();
|
||||
}
|
||||
|
||||
Widget _buildActionChip(
|
||||
SortField sortField, SortField? currentlySelectedOrder, BuildContext context) {
|
||||
Widget _buildActionChip(SortField sortField,
|
||||
SortField? currentlySelectedOrder, BuildContext context) {
|
||||
String text;
|
||||
switch (sortField) {
|
||||
case SortField.archiveSerialNumber:
|
||||
@@ -488,8 +508,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
color: Colors.green,
|
||||
)
|
||||
: null,
|
||||
onPressed: () =>
|
||||
docBloc.updateFilter(filter: docBloc.state.filter.copyWith(sortField: sortField)),
|
||||
onPressed: () => docBloc.updateFilter(
|
||||
filter: docBloc.state.filter.copyWith(sortField: sortField)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -510,7 +530,9 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
||||
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
||||
);
|
||||
BlocProvider.of<DocumentsCubit>(context).updateFilter(filter: newFilter).then((value) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: newFilter)
|
||||
.then((value) {
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.panelController.close();
|
||||
|
||||
@@ -20,19 +20,23 @@ class QueryTypeFormField extends StatelessWidget {
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel),
|
||||
title: Text(S
|
||||
.of(context)
|
||||
.documentsFilterPageQueryOptionsTitleAndContentLabel),
|
||||
),
|
||||
value: QueryType.titleAndContent,
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context).documentsFilterPageQueryOptionsTitleLabel),
|
||||
title:
|
||||
Text(S.of(context).documentsFilterPageQueryOptionsTitleLabel),
|
||||
),
|
||||
value: QueryType.title,
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context).documentsFilterPageQueryOptionsExtendedLabel),
|
||||
title: Text(
|
||||
S.of(context).documentsFilterPageQueryOptionsExtendedLabel),
|
||||
),
|
||||
value: QueryType.extended,
|
||||
),
|
||||
|
||||
@@ -76,7 +76,8 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
|
||||
SavedView.fromDocumentFilter(
|
||||
widget.currentFilter,
|
||||
name: _formKey.currentState?.value[fkName] as String,
|
||||
showOnDashboard: _formKey.currentState?.value[fkShowOnDashboard] as bool,
|
||||
showOnDashboard:
|
||||
_formKey.currentState?.value[fkShowOnDashboard] as bool,
|
||||
showInSidebar: _formKey.currentState?.value[fkShowInSidebar] as bool,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -6,7 +6,8 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
static const _bulletPoint = "\u2022";
|
||||
final DocumentsState state;
|
||||
const BulkDeleteConfirmationDialog({Key? key, required this.state}) : super(key: key);
|
||||
const BulkDeleteConfirmationDialog({Key? key, required this.state})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,8 +20,12 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
Text(
|
||||
//TODO: use plurals, didn't use because of crash... investigate later.
|
||||
state.selection.length == 1
|
||||
? S.of(context).documentsPageSelectionBulkDeleteDialogWarningTextOne
|
||||
: S.of(context).documentsPageSelectionBulkDeleteDialogWarningTextMany,
|
||||
? S
|
||||
.of(context)
|
||||
.documentsPageSelectionBulkDeleteDialogWarningTextOne
|
||||
: S
|
||||
.of(context)
|
||||
.documentsPageSelectionBulkDeleteDialogWarningTextMany,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ConstrainedBox(
|
||||
@@ -31,7 +36,8 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
||||
Text(
|
||||
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -41,7 +47,8 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(Theme.of(context).colorScheme.error),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
|
||||
@@ -36,10 +36,11 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
expandedHeight: kToolbarHeight,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => BlocProvider.of<DocumentsCubit>(context).resetSelection(),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<DocumentsCubit>(context).resetSelection(),
|
||||
),
|
||||
title:
|
||||
Text('${documentsState.selection.length} ${S.of(context).documentsSelectedText}'),
|
||||
title: Text(
|
||||
'${documentsState.selection.length} ${S.of(context).documentsSelectedText}'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
@@ -79,9 +80,8 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
if (shouldDelete ?? false) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkRemoveDocuments(documentsState.selection)
|
||||
.then((_) => showSnackBar(context, S.of(context).documentsPageBulkDeleteSuccessfulText))
|
||||
.onError<ErrorMessage>(
|
||||
(error, _) => showSnackBar(context, translateError(context, error.code)));
|
||||
.then((_) => showSnackBar(
|
||||
context, S.of(context).documentsPageBulkDeleteSuccessfulText));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
child: FilterChip(
|
||||
label: Text(state.value.values.toList()[index].name),
|
||||
selected: view.id == state.selectedSavedViewId,
|
||||
onSelected: (isSelected) => _onSelected(isSelected, context, view),
|
||||
onSelected: (isSelected) =>
|
||||
_onSelected(isSelected, context, view),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -76,21 +77,19 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
void _onCreatePressed(BuildContext context) async {
|
||||
final newView = await Navigator.of(context).push<SavedView?>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddSavedViewPage(currentFilter: getIt<DocumentsCubit>().state.filter),
|
||||
builder: (context) => AddSavedViewPage(
|
||||
currentFilter: getIt<DocumentsCubit>().state.filter),
|
||||
),
|
||||
);
|
||||
if (newView != null) {
|
||||
try {
|
||||
BlocProvider.of<SavedViewCubit>(context).add(newView);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
BlocProvider.of<SavedViewCubit>(context).add(newView);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(bool isSelected, BuildContext context, SavedView view) {
|
||||
if (isSelected) {
|
||||
BlocProvider.of<DocumentsCubit>(context).updateFilter(filter: view.toDocumentFilter());
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: view.toDocumentFilter());
|
||||
BlocProvider.of<SavedViewCubit>(context).selectView(view);
|
||||
} else {
|
||||
BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
@@ -106,11 +105,7 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
) ??
|
||||
false;
|
||||
if (delete) {
|
||||
try {
|
||||
BlocProvider.of<SavedViewCubit>(context).remove(view);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
BlocProvider.of<SavedViewCubit>(context).remove(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_order.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class SortDocumentsButton extends StatefulWidget {
|
||||
const SortDocumentsButton({
|
||||
@@ -30,16 +33,20 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final bool isAscending = state.filter.sortOrder == SortOrder.ascending;
|
||||
final bool isAscending =
|
||||
state.filter.sortOrder == SortOrder.ascending;
|
||||
child = IconButton(
|
||||
icon: FaIcon(
|
||||
isAscending ? FontAwesomeIcons.arrowDownAZ : FontAwesomeIcons.arrowUpZA,
|
||||
isAscending
|
||||
? FontAwesomeIcons.arrowDownAZ
|
||||
: FontAwesomeIcons.arrowUpZA,
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() => _isLoading = true);
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(
|
||||
filter: state.filter.copyWith(sortOrder: state.filter.sortOrder.toggle()))
|
||||
filter: state.filter
|
||||
.copyWith(sortOrder: state.filter.sortOrder.toggle()))
|
||||
.whenComplete(() => setState(() => _isLoading = false));
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user