feat: Further migrations to go_router, add onclick to document previews

This commit is contained in:
Anton Stubenbord
2023-07-31 02:51:00 +02:00
parent f1398e6d4c
commit f3e660e91d
33 changed files with 868 additions and 845 deletions

View File

@@ -1,15 +1,8 @@
import 'dart:typed_data';
import 'package:connectivity_plus/connectivity_plus.dart';
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_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart'; import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
@@ -18,14 +11,7 @@ import 'package:paperless_mobile/core/repository/user_repository.dart';
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart'; import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_label_page.dart'; import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_label_page.dart';
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart'; import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
import 'package:paperless_mobile/features/home/view/model/api_version.dart'; import 'package:paperless_mobile/features/home/view/model/api_version.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart'; import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart'; import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
@@ -33,29 +19,6 @@ import 'package:paperless_mobile/features/saved_view_details/view/saved_view_det
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// These are convenience methods for nativating to views without having to pass providers around explicitly.
// Providers unfortunately have to be passed to the routes since they are children of the Navigator, not ancestors.
Future<void> pushDocumentSearchPage(BuildContext context) {
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
.getValue()!
.loggedInUserId;
final userRepo = context.read<UserRepository>();
return Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => DocumentSearchCubit(
context.read(),
context.read(),
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
.get(currentUser)!,
),
child: const DocumentSearchPage(),
),
),
);
}
Future<void> pushSavedViewDetailsRoute( Future<void> pushSavedViewDetailsRoute(
BuildContext context, { BuildContext context, {
required SavedView savedView, required SavedView savedView,
@@ -84,7 +47,8 @@ Future<void> pushSavedViewDetailsRoute(
savedView: savedView, savedView: savedView,
), ),
child: SavedViewDetailsPage( child: SavedViewDetailsPage(
onDelete: context.read<SavedViewCubit>().remove), onDelete: context.read<SavedViewCubit>().remove,
),
); );
}, },
), ),
@@ -107,39 +71,6 @@ Future<SavedView?> pushAddSavedViewRoute(BuildContext context,
); );
} }
Future<void> pushLinkedDocumentsView(
BuildContext context, {
required DocumentFilter filter,
}) {
return Navigator.push(
context,
MaterialPageRoute(
builder: (_) => MultiProvider(
providers: [
Provider.value(value: context.read<ApiVersion>()),
Provider.value(value: context.read<LabelRepository>()),
Provider.value(value: context.read<DocumentChangedNotifier>()),
Provider.value(value: context.read<PaperlessDocumentsApi>()),
Provider.value(value: context.read<LocalNotificationService>()),
Provider.value(value: context.read<CacheManager>()),
Provider.value(value: context.read<ConnectivityCubit>()),
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
Provider.value(value: context.read<UserRepository>()),
],
builder: (context, _) => BlocProvider(
create: (context) => LinkedDocumentsCubit(
filter,
context.read(),
context.read(),
context.read(),
),
child: const LinkedDocumentsPage(),
),
),
),
);
}
Future<void> pushBulkEditCorrespondentRoute( Future<void> pushBulkEditCorrespondentRoute(
BuildContext context, { BuildContext context, {
required List<DocumentModel> selection, required List<DocumentModel> selection,
@@ -306,44 +237,6 @@ Future<void> pushBulkEditDocumentTypeRoute(BuildContext context,
); );
} }
Future<DocumentUploadResult?> pushDocumentUploadPreparationPage(
BuildContext context, {
required Uint8List bytes,
String? filename,
String? fileExtension,
String? title,
}) {
final labelRepo = context.read<LabelRepository>();
final docsApi = context.read<PaperlessDocumentsApi>();
final connectivity = context.read<Connectivity>();
final apiVersion = context.read<ApiVersion>();
return Navigator.of(context).push<DocumentUploadResult>(
MaterialPageRoute(
builder: (_) => MultiProvider(
providers: [
Provider.value(value: labelRepo),
Provider.value(value: docsApi),
Provider.value(value: connectivity),
Provider.value(value: apiVersion)
],
builder: (_, child) => BlocProvider(
create: (_) => DocumentUploadCubit(
context.read(),
context.read(),
context.read(),
),
child: DocumentUploadPreparationPage(
fileBytes: bytes,
fileExtension: fileExtension,
filename: filename,
title: title,
),
),
),
),
);
}
List<Provider> _getRequiredBulkEditProviders(BuildContext context) { List<Provider> _getRequiredBulkEditProviders(BuildContext context) {
return [ return [
Provider.value(value: context.read<PaperlessDocumentsApi>()), Provider.value(value: context.read<PaperlessDocumentsApi>()),

View File

@@ -1,5 +1,6 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart'; import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
import 'package:paperless_mobile/extensions/dart_extensions.dart'; import 'package:paperless_mobile/extensions/dart_extensions.dart';
@@ -86,6 +87,7 @@ class _FullscreenBulkEditLabelPageState<T extends Label>
selectionCount: _labels.length, selectionCount: _labels.length,
floatingActionButton: !hideFab floatingActionButton: !hideFab
? FloatingActionButton.extended( ? FloatingActionButton.extended(
heroTag: "fab_fullscreen_bulk_edit_label",
onPressed: _onSubmit, onPressed: _onSubmit,
label: Text(S.of(context)!.apply), label: Text(S.of(context)!.apply),
icon: const Icon(Icons.done), icon: const Icon(Icons.done),
@@ -122,7 +124,7 @@ class _FullscreenBulkEditLabelPageState<T extends Label>
void _onSubmit() async { void _onSubmit() async {
if (_selection == null) { if (_selection == null) {
Navigator.pop(context); context.pop();
} else { } else {
bool shouldPerformAction; bool shouldPerformAction;
if (_selection!.label == null) { if (_selection!.label == null) {
@@ -148,7 +150,7 @@ class _FullscreenBulkEditLabelPageState<T extends Label>
} }
if (shouldPerformAction) { if (shouldPerformAction) {
widget.onSubmit(_selection!.label); widget.onSubmit(_selection!.label);
Navigator.pop(context); context.pop();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
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:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart'; import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
import 'package:paperless_mobile/extensions/dart_extensions.dart'; import 'package:paperless_mobile/extensions/dart_extensions.dart';
@@ -74,6 +75,7 @@ class _FullscreenBulkEditTagsWidgetState
controller: _controller, controller: _controller,
floatingActionButton: _addTags.isNotEmpty || _removeTags.isNotEmpty floatingActionButton: _addTags.isNotEmpty || _removeTags.isNotEmpty
? FloatingActionButton.extended( ? FloatingActionButton.extended(
heroTag: "fab_fullscreen_bulk_edit_tags",
label: Text(S.of(context)!.apply), label: Text(S.of(context)!.apply),
icon: const Icon(Icons.done), icon: const Icon(Icons.done),
onPressed: _submit, onPressed: _submit,
@@ -173,7 +175,7 @@ class _FullscreenBulkEditTagsWidgetState
removeTagIds: _removeTags, removeTagIds: _removeTags,
addTagIds: _addTags, addTagIds: _addTags,
); );
Navigator.pop(context); context.pop();
} }
} }
} }

View File

@@ -1,5 +1,6 @@
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:go_router/go_router.dart';
import 'package:open_filex/open_filex.dart'; import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
@@ -92,37 +93,43 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
DocumentDetailsState>( DocumentDetailsState>(
builder: (context, state) { builder: (context, state) {
return Positioned.fill( return Positioned.fill(
child: GestureDetector(
onTap: () {
DocumentPreviewRoute($extra: state.document)
.push(context);
},
child: DocumentPreview( child: DocumentPreview(
document: state.document, document: state.document,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
),
); );
}, },
), ),
Positioned.fill( // Positioned.fill(
top: 0, // top: -kToolbarHeight,
child: DecoratedBox( // child: DecoratedBox(
decoration: BoxDecoration( // decoration: BoxDecoration(
gradient: LinearGradient( // gradient: LinearGradient(
colors: [ // colors: [
Theme.of(context) // Theme.of(context)
.colorScheme // .colorScheme
.background // .background
.withOpacity(0.8), // .withOpacity(0.8),
Theme.of(context) // Theme.of(context)
.colorScheme // .colorScheme
.background // .background
.withOpacity(0.5), // .withOpacity(0.5),
Colors.transparent, // Colors.transparent,
Colors.transparent, // Colors.transparent,
Colors.transparent, // Colors.transparent,
], // ],
begin: Alignment.topCenter, // begin: Alignment.topCenter,
end: Alignment.bottomCenter, // end: Alignment.bottomCenter,
), // ),
), // ),
), // ),
), // ),
], ],
), ),
), ),
@@ -302,6 +309,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
preferBelow: false, preferBelow: false,
verticalOffset: 40, verticalOffset: 40,
child: FloatingActionButton( child: FloatingActionButton(
heroTag: "fab_document_details",
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
onPressed: () => EditDocumentRoute(state.document).push(context), onPressed: () => EditDocumentRoute(state.document).push(context),
), ),
@@ -333,13 +341,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
document: state.document, document: state.document,
enabled: isConnected, enabled: isConnected,
), ),
//TODO: Enable again, need new pdf viewer package... // //TODO: Enable again, need new pdf viewer package...
IconButton( // IconButton(
tooltip: S.of(context)!.previewTooltip, // tooltip: S.of(context)!.previewTooltip,
icon: const Icon(Icons.visibility), // icon: const Icon(Icons.visibility),
onPressed: // onPressed:
(isConnected) ? () => _onOpen(state.document) : null, // (isConnected) ? () => _onOpen(state.document) : null,
).paddedOnly(right: 4.0), // ).paddedOnly(right: 4.0),
IconButton( IconButton(
tooltip: S.of(context)!.openInSystemViewer, tooltip: S.of(context)!.openInSystemViewer,
icon: const Icon(Icons.open_in_new), icon: const Icon(Icons.open_in_new),
@@ -391,21 +399,17 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} finally { } finally {
// Document deleted => go back to primary route do {
Navigator.popUntil(context, (route) => route.isFirst); context.pop();
} while (context.canPop());
} }
} }
} }
Future<void> _onOpen(DocumentModel document) async { Future<void> _onOpen(DocumentModel document) async {
Navigator.of(context).push( DocumentPreviewRoute(
MaterialPageRoute( $extra: document,
builder: (_) => DocumentView(
documentBytes:
context.read<PaperlessDocumentsApi>().download(document),
title: document.title, title: document.title,
), ).push(context);
),
);
} }
} }

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/archive_serial_number_field.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/archive_serial_number_field.dart';
@@ -25,6 +26,7 @@ class DocumentMetaDataWidget extends StatefulWidget {
class _DocumentMetaDataWidgetState extends State<DocumentMetaDataWidget> { class _DocumentMetaDataWidgetState extends State<DocumentMetaDataWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>( return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) { builder: (context, state) {
if (state.metaData == null) { if (state.metaData == null) {
@@ -37,6 +39,7 @@ class _DocumentMetaDataWidgetState extends State<DocumentMetaDataWidget> {
return SliverList( return SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
if (currentUser.canEditDocuments)
ArchiveSerialNumberField( ArchiveSerialNumberField(
document: widget.document, document: widget.document,
).paddedOnly(bottom: widget.itemSpacing), ).paddedOnly(bottom: widget.itemSpacing),

View File

@@ -31,9 +31,8 @@ class DocumentOverviewWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverList( return SliverList.list(
delegate: SliverChildListDelegate( children: [
[
DetailsItem( DetailsItem(
label: S.of(context)!.title, label: S.of(context)!.title,
content: HighlightedText( content: HighlightedText(
@@ -72,10 +71,7 @@ class DocumentOverviewWidget extends StatelessWidget {
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
if (document.storagePath != null && if (document.storagePath != null &&
context context.watch<LocalUserAccount>().paperlessUser.canViewStoragePaths)
.watch<LocalUserAccount>()
.paperlessUser
.canViewStoragePaths)
DetailsItem( DetailsItem(
label: S.of(context)!.storagePath, label: S.of(context)!.storagePath,
content: LabelText<StoragePath>( content: LabelText<StoragePath>(
@@ -95,7 +91,6 @@ class DocumentOverviewWidget extends StatelessWidget {
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
], ],
),
); );
} }
} }

View File

@@ -4,6 +4,7 @@ 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_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
@@ -44,6 +45,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
return BlocBuilder<DocumentEditCubit, DocumentEditState>( return BlocBuilder<DocumentEditCubit, DocumentEditState>(
builder: (context, state) { builder: (context, state) {
final filteredSuggestions = state.suggestions?.documentDifference( final filteredSuggestions = state.suggestions?.documentDifference(
@@ -53,6 +55,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: Scaffold( child: Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
heroTag: "fab_document_edit",
onPressed: () => _onSubmit(state.document), onPressed: () => _onSubmit(state.document),
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: Text(S.of(context)!.saveChanges), label: Text(S.of(context)!.saveChanges),
@@ -90,6 +93,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
filteredSuggestions, filteredSuggestions,
).padded(), ).padded(),
// Correspondent form field // Correspondent form field
if (currentUser.canViewCorrespondents)
Column( Column(
children: [ children: [
LabelFormField<Correspondent>( LabelFormField<Correspondent>(
@@ -116,10 +120,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkCorrespondent, name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined), prefixIcon: const Icon(Icons.person_outlined),
allowSelectUnassigned: true, allowSelectUnassigned: true,
canCreateNewLabel: context canCreateNewLabel:
.watch<LocalUserAccount>() currentUser.canCreateCorrespondents,
.paperlessUser
.canCreateCorrespondents,
), ),
if (filteredSuggestions if (filteredSuggestions
?.hasSuggestedCorrespondents ?? ?.hasSuggestedCorrespondents ??
@@ -132,8 +134,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
label: Text( label: Text(
state.correspondents[itemData]!.name), state.correspondents[itemData]!.name),
onPressed: () { onPressed: () {
_formKey _formKey.currentState
.currentState?.fields[fkCorrespondent] ?.fields[fkCorrespondent]
?.didChange( ?.didChange(
IdQueryParameter.fromId(itemData), IdQueryParameter.fromId(itemData),
); );
@@ -143,6 +145,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
], ],
).padded(), ).padded(),
// DocumentType form field // DocumentType form field
if (currentUser.canViewDocumentTypes)
Column( Column(
children: [ children: [
LabelFormField<DocumentType>( LabelFormField<DocumentType>(
@@ -155,10 +158,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
initialName: currentInput, initialName: currentInput,
), ),
), ),
canCreateNewLabel: context canCreateNewLabel:
.watch<LocalUserAccount>() currentUser.canCreateDocumentTypes,
.paperlessUser
.canCreateDocumentTypes,
addLabelText: S.of(context)!.addDocumentType, addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType, labelText: S.of(context)!.documentType,
initialValue: initialValue:
@@ -192,6 +193,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
], ],
).padded(), ).padded(),
// StoragePath form field // StoragePath form field
if (currentUser.canViewStoragePaths)
Column( Column(
children: [ children: [
LabelFormField<StoragePath>( LabelFormField<StoragePath>(
@@ -203,14 +205,13 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddStoragePathPage( child: AddStoragePathPage(
initialName: initialValue), initialName: initialValue),
), ),
canCreateNewLabel: context canCreateNewLabel:
.watch<LocalUserAccount>() currentUser.canCreateStoragePaths,
.paperlessUser
.canCreateStoragePaths,
addLabelText: S.of(context)!.addStoragePath, addLabelText: S.of(context)!.addStoragePath,
labelText: S.of(context)!.storagePath, labelText: S.of(context)!.storagePath,
options: state.storagePaths, options: state.storagePaths,
initialValue: state.document.storagePath != null initialValue:
state.document.storagePath != null
? IdQueryParameter.fromId( ? IdQueryParameter.fromId(
state.document.storagePath!) state.document.storagePath!)
: const IdQueryParameter.unset(), : const IdQueryParameter.unset(),
@@ -221,6 +222,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
], ],
).padded(), ).padded(),
// Tag form field // Tag form field
if (currentUser.canViewTags)
TagsFormField( TagsFormField(
options: state.tags, options: state.tags,
name: fkTags, name: fkTags,
@@ -321,7 +323,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
setState(() { setState(() {
_isSubmitLoading = false; _isSubmitLoading = false;
}); });
Navigator.pop(context); context.pop();
} }
} }
} }

View File

@@ -22,11 +22,13 @@ import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_c
import 'package:paperless_mobile/features/document_scan/view/widgets/export_scans_dialog.dart'; import 'package:paperless_mobile/features/document_scan/view/widgets/export_scans_dialog.dart';
import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart'; import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart';
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/helpers/permission_helpers.dart'; import 'package:paperless_mobile/helpers/permission_helpers.dart';
import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:pdf/pdf.dart'; import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
@@ -53,18 +55,13 @@ class _ScannerPageState extends State<ScannerPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectedState) { builder: (context, connectedState) {
return Scaffold( return BlocBuilder<DocumentScannerCubit, List<File>>(
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton(
onPressed: () => _openDocumentScanner(context),
child: const Icon(Icons.add_a_photo_outlined),
),
body: BlocBuilder<DocumentScannerCubit, List<File>>(
builder: (context, state) { builder: (context, state) {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
drawer: const AppDrawer(), drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: "fab_document_edit",
onPressed: () => _openDocumentScanner(context), onPressed: () => _openDocumentScanner(context),
child: const Icon(Icons.add_a_photo_outlined), child: const Icon(Icons.add_a_photo_outlined),
), ),
@@ -104,7 +101,6 @@ class _ScannerPageState extends State<ScannerPage>
), ),
); );
}, },
),
); );
}, },
); );
@@ -260,11 +256,10 @@ class _ScannerPageState extends State<ScannerPage>
.getValue()! .getValue()!
.enforceSinglePagePdfUpload, .enforceSinglePagePdfUpload,
); );
final uploadResult = await pushDocumentUploadPreparationPage( final uploadResult = await DocumentUploadRoute(
context, $extra: file.bytes,
bytes: file.bytes,
fileExtension: file.extension, fileExtension: file.extension,
); ).push<DocumentUploadResult>(context);
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) { if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
// For paperless version older than 1.11.3, task id will always be null! // For paperless version older than 1.11.3, task id will always be null!
context.read<DocumentScannerCubit>().reset(); context.read<DocumentScannerCubit>().reset();
@@ -366,13 +361,12 @@ class _ScannerPageState extends State<ScannerPage>
); );
return; return;
} }
pushDocumentUploadPreparationPage( DocumentUploadRoute(
context, $extra: file.readAsBytesSync(),
bytes: file.readAsBytesSync(),
filename: fileDescription.filename, filename: fileDescription.filename,
title: fileDescription.filename, title: fileDescription.filename,
fileExtension: fileDescription.extension, fileExtension: fileDescription.extension,
); ).push(context);
} }
} }

View File

@@ -1,18 +1,12 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart'; import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/user_repository.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart'; import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart'; import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart'; import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart'; import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -85,24 +79,14 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
); );
}, },
openBuilder: (_, action) { openBuilder: (_, action) {
return MultiProvider( return Provider(
providers: [
Provider.value(value: context.read<LabelRepository>()),
Provider.value(value: context.read<PaperlessDocumentsApi>()),
Provider.value(value: context.read<CacheManager>()),
Provider.value(value: context.read<ApiVersion>()),
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
Provider.value(value: context.read<UserRepository>()),
],
child: Provider(
create: (_) => DocumentSearchCubit( create: (_) => DocumentSearchCubit(
context.read(), context.read(),
context.read(), context.read(),
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState) Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
.get(context.watch<LocalUserAccount>().id)!, .get(context.read<LocalUserAccount>().id)!,
),
builder: (_, __) => const DocumentSearchPage(),
), ),
child: const DocumentSearchPage(),
); );
}, },
), ),
@@ -114,11 +98,10 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
icon: UserAvatar(account: context.watch<LocalUserAccount>()), icon: UserAvatar(account: context.watch<LocalUserAccount>()),
onPressed: () { onPressed: () {
final apiVersion = context.read<ApiVersion>();
showDialog( showDialog(
context: context, context: context,
builder: (context) => Provider.value( builder: (_) => Provider.value(
value: apiVersion, value: context.read<LocalUserAccount>(),
child: const ManageAccountsPage(), child: const ManageAccountsPage(),
), ),
); );

View File

@@ -56,11 +56,10 @@ class SliverSearchBar extends StatelessWidget {
}, },
), ),
onPressed: () { onPressed: () {
final apiVersion = context.read<ApiVersion>();
showDialog( showDialog(
context: context, context: context,
builder: (context) => Provider.value( builder: (_) => Provider.value(
value: apiVersion, value: context.read<LocalUserAccount>(),
child: const ManageAccountsPage(), child: const ManageAccountsPage(),
), ),
); );

View File

@@ -3,8 +3,9 @@ import 'dart:typed_data';
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_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:image/image.dart' as img;
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
@@ -56,7 +57,7 @@ class _DocumentUploadPreparationPageState
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss"); static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
final GlobalKey<FormBuilderState> _formKey = GlobalKey(); final GlobalKey<FormBuilderState> _formKey = GlobalKey();
Color? _titleColor;
Map<String, String> _errors = {}; Map<String, String> _errors = {};
bool _isUploadLoading = false; bool _isUploadLoading = false;
late bool _syncTitleAndFilename; late bool _syncTitleAndFilename;
@@ -67,24 +68,21 @@ class _DocumentUploadPreparationPageState
void initState() { void initState() {
super.initState(); super.initState();
_syncTitleAndFilename = widget.filename == null && widget.title == null; _syncTitleAndFilename = widget.filename == null && widget.title == null;
_titleColor = _computeAverageColor().computeLuminance() > 0.5
? Colors.black
: Colors.white;
initializeDateFormatting(); initializeDateFormatting();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
extendBodyBehindAppBar: false,
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text(S.of(context)!.prepareDocument),
bottom: _isUploadLoading
? const PreferredSize(
child: LinearProgressIndicator(),
preferredSize: Size.fromHeight(4.0))
: null,
),
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0, visible: MediaQuery.of(context).viewInsets.bottom == 0,
child: FloatingActionButton.extended( child: FloatingActionButton.extended(
heroTag: "fab_document_upload",
onPressed: _onSubmit, onPressed: _onSubmit,
label: Text(S.of(context)!.upload), label: Text(S.of(context)!.upload),
icon: const Icon(Icons.upload), icon: const Icon(Icons.upload),
@@ -94,14 +92,57 @@ class _DocumentUploadPreparationPageState
builder: (context, state) { builder: (context, state) {
return FormBuilder( return FormBuilder(
key: _formKey, key: _formKey,
child: ListView( child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
leading: BackButton(
color: _titleColor,
),
pinned: true,
expandedHeight: 150,
flexibleSpace: FlexibleSpaceBar(
background: Image.memory(
widget.fileBytes,
fit: BoxFit.cover,
),
title: Text(
S.of(context)!.prepareDocument,
style: TextStyle(
color: _titleColor,
),
),
),
bottom: _isUploadLoading
? const PreferredSize(
child: LinearProgressIndicator(),
preferredSize: Size.fromHeight(4.0),
)
: null,
),
),
],
body: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Builder(
builder: (context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverList.list(
children: [ children: [
// Title // Title
FormBuilderTextField( FormBuilderTextField(
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
name: DocumentModel.titleKey, name: DocumentModel.titleKey,
initialValue: initialValue: widget.title ??
widget.title ?? "scan_${fileNameDateFormat.format(_now)}", "scan_${fileNameDateFormat.format(_now)}",
validator: (value) { validator: (value) {
if (value?.trim().isEmpty ?? true) { if (value?.trim().isEmpty ?? true) {
return S.of(context)!.thisFieldIsRequired; return S.of(context)!.thisFieldIsRequired;
@@ -113,7 +154,8 @@ class _DocumentUploadPreparationPageState
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () { onPressed: () {
_formKey.currentState?.fields[DocumentModel.titleKey] _formKey.currentState
?.fields[DocumentModel.titleKey]
?.didChange(""); ?.didChange("");
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
_formKey.currentState?.fields[fkFileName] _formKey.currentState?.fields[fkFileName]
@@ -143,7 +185,8 @@ class _DocumentUploadPreparationPageState
suffixText: widget.fileExtension, suffixText: widget.fileExtension,
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkFileName] onPressed: () => _formKey
.currentState?.fields[fkFileName]
?.didChange(''), ?.didChange(''),
), ),
), ),
@@ -158,7 +201,8 @@ class _DocumentUploadPreparationPageState
() => _syncTitleAndFilename = value, () => _syncTitleAndFilename = value,
); );
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
final String transformedValue = _formatFilename(_formKey final String transformedValue =
_formatFilename(_formKey
.currentState .currentState
?.fields[DocumentModel.titleKey] ?.fields[DocumentModel.titleKey]
?.value as String); ?.value as String);
@@ -180,10 +224,12 @@ class _DocumentUploadPreparationPageState
name: DocumentModel.createdKey, name: DocumentModel.createdKey,
initialValue: null, initialValue: null,
onChanged: (value) { onChanged: (value) {
setState(() => _showDatePickerDeleteIcon = value != null); setState(() =>
_showDatePickerDeleteIcon = value != null);
}, },
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month_outlined), prefixIcon:
const Icon(Icons.calendar_month_outlined),
labelText: S.of(context)!.createdAt + " *", labelText: S.of(context)!.createdAt + " *",
suffixIcon: _showDatePickerDeleteIcon suffixIcon: _showDatePickerDeleteIcon
? IconButton( ? IconButton(
@@ -205,7 +251,8 @@ class _DocumentUploadPreparationPageState
LabelFormField<Correspondent>( LabelFormField<Correspondent>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => MultiProvider( addLabelPageBuilder: (initialName) =>
MultiProvider(
providers: [ providers: [
Provider.value( Provider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
@@ -214,7 +261,8 @@ class _DocumentUploadPreparationPageState
value: context.read<ApiVersion>(), value: context.read<ApiVersion>(),
) )
], ],
child: AddCorrespondentPage(initialName: initialName), child: AddCorrespondentPage(
initialName: initialName),
), ),
addLabelText: S.of(context)!.addCorrespondent, addLabelText: S.of(context)!.addCorrespondent,
labelText: S.of(context)!.correspondent + " *", labelText: S.of(context)!.correspondent + " *",
@@ -235,7 +283,8 @@ class _DocumentUploadPreparationPageState
LabelFormField<DocumentType>( LabelFormField<DocumentType>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => MultiProvider( addLabelPageBuilder: (initialName) =>
MultiProvider(
providers: [ providers: [
Provider.value( Provider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
@@ -244,20 +293,25 @@ class _DocumentUploadPreparationPageState
value: context.read<ApiVersion>(), value: context.read<ApiVersion>(),
) )
], ],
child: AddDocumentTypePage(initialName: initialName), child: AddDocumentTypePage(
initialName: initialName),
), ),
addLabelText: S.of(context)!.addDocumentType, addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType + " *", labelText: S.of(context)!.documentType + " *",
name: DocumentModel.documentTypeKey, name: DocumentModel.documentTypeKey,
options: state.documentTypes, options: state.documentTypes,
prefixIcon: const Icon(Icons.description_outlined), prefixIcon:
const Icon(Icons.description_outlined),
allowSelectUnassigned: true, allowSelectUnassigned: true,
canCreateNewLabel: context canCreateNewLabel: context
.watch<LocalUserAccount>() .watch<LocalUserAccount>()
.paperlessUser .paperlessUser
.canCreateDocumentTypes, .canCreateDocumentTypes,
), ),
if (context.watch<LocalUserAccount>().paperlessUser.canViewTags) if (context
.watch<LocalUserAccount>()
.paperlessUser
.canViewTags)
TagsFormField( TagsFormField(
name: DocumentModel.tagsKey, name: DocumentModel.tagsKey,
allowCreation: true, allowCreation: true,
@@ -268,10 +322,17 @@ class _DocumentUploadPreparationPageState
Text( Text(
"* " + S.of(context)!.uploadInferValuesHint, "* " + S.of(context)!.uploadInferValuesHint,
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), textAlign: TextAlign.justify,
).padded(),
const SizedBox(height: 300), const SizedBox(height: 300),
].padded(), ].padded(),
), ),
],
);
},
),
),
),
); );
}, },
), ),
@@ -317,10 +378,7 @@ class _DocumentUploadPreparationPageState
context, context,
S.of(context)!.documentSuccessfullyUploadedProcessing, S.of(context)!.documentSuccessfullyUploadedProcessing,
); );
Navigator.pop( context.pop(DocumentUploadResult(true, taskId));
context,
DocumentUploadResult(true, taskId),
);
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} on PaperlessFormValidationException catch (exception) { } on PaperlessFormValidationException catch (exception) {
@@ -345,4 +403,33 @@ class _DocumentUploadPreparationPageState
String _formatFilename(String source) { String _formatFilename(String source) {
return source.replaceAll(RegExp(r"[\W_]"), "_").toLowerCase(); return source.replaceAll(RegExp(r"[\W_]"), "_").toLowerCase();
} }
Color _computeAverageColor() {
final bitmap = img.decodeImage(widget.fileBytes);
if (bitmap == null) {
return Colors.black;
}
int redBucket = 0;
int greenBucket = 0;
int blueBucket = 0;
int pixelCount = 0;
for (int y = 0; y < bitmap.height; y++) {
for (int x = 0; x < bitmap.width; x++) {
final c = bitmap.getPixel(x, y);
pixelCount++;
redBucket += c.r.toInt();
greenBucket += c.g.toInt();
blueBucket += c.b.toInt();
}
}
return Color.fromRGBO(
redBucket ~/ pixelCount,
greenBucket ~/ pixelCount,
blueBucket ~/ pixelCount,
1,
);
}
} }

View File

@@ -134,7 +134,7 @@ class _DocumentsPageState extends State<DocumentsPage>
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: FloatingActionButton.small( child: FloatingActionButton.small(
key: UniqueKey(), heroTag: "fab_documents_page_reset_filter",
backgroundColor: Theme.of(context) backgroundColor: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer, .onPrimaryContainer,
@@ -164,11 +164,13 @@ class _DocumentsPageState extends State<DocumentsPage>
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
child: (_currentTab == 0) child: (_currentTab == 0)
? FloatingActionButton( ? FloatingActionButton(
heroTag: "fab_documents_page_filter",
child: child:
const Icon(Icons.filter_alt_outlined), const Icon(Icons.filter_alt_outlined),
onPressed: _openDocumentFilter, onPressed: _openDocumentFilter,
) )
: FloatingActionButton( : FloatingActionButton(
heroTag: "fab_documents_page_filter",
child: const Icon(Icons.add), child: const Icon(Icons.add),
onPressed: () => onPressed: () =>
_onCreateSavedView(state.filter), _onCreateSavedView(state.filter),

View File

@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
@@ -12,6 +13,7 @@ class DocumentPreview extends StatelessWidget {
final double borderRadius; final double borderRadius;
final bool enableHero; final bool enableHero;
final double scale; final double scale;
final bool isClickable;
const DocumentPreview({ const DocumentPreview({
super.key, super.key,
@@ -21,16 +23,24 @@ class DocumentPreview extends StatelessWidget {
this.borderRadius = 12.0, this.borderRadius = 12.0,
this.enableHero = true, this.enableHero = true,
this.scale = 1.1, this.scale = 1.1,
this.isClickable = true,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return HeroMode( return GestureDetector(
onTap: isClickable
? () {
DocumentPreviewRoute($extra: document).push(context);
}
: null,
child: HeroMode(
enabled: enableHero, enabled: enableHero,
child: Hero( child: Hero(
tag: "thumb_${document.id}", tag: "thumb_${document.id}",
child: _buildPreview(context), child: _buildPreview(context),
), ),
),
); );
} }

View File

@@ -80,6 +80,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0, visible: MediaQuery.of(context).viewInsets.bottom == 0,
child: FloatingActionButton.extended( child: FloatingActionButton.extended(
heroTag: "fab_document_filter_panel",
icon: const Icon(Icons.done), icon: const Icon(Icons.done),
label: Text(S.of(context)!.apply), label: Text(S.of(context)!.apply),
onPressed: _onApplyFilter, onPressed: _onApplyFilter,

View File

@@ -7,6 +7,7 @@ import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart'; import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
class DocumentSelectionSliverAppBar extends StatelessWidget { class DocumentSelectionSliverAppBar extends StatelessWidget {
final DocumentsState state; final DocumentsState state;
@@ -65,24 +66,30 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
label: Text(S.of(context)!.correspondent), label: Text(S.of(context)!.correspondent),
avatar: const Icon(Icons.edit), avatar: const Icon(Icons.edit),
onPressed: () { onPressed: () {
pushBulkEditCorrespondentRoute(context, BulkEditDocumentsRoute(BulkEditExtraWrapper(
selection: state.selection); state.selection,
LabelType.correspondent,
)).push(context);
}, },
).paddedOnly(left: 8, right: 4), ).paddedOnly(left: 8, right: 4),
ActionChip( ActionChip(
label: Text(S.of(context)!.documentType), label: Text(S.of(context)!.documentType),
avatar: const Icon(Icons.edit), avatar: const Icon(Icons.edit),
onPressed: () async { onPressed: () async {
pushBulkEditDocumentTypeRoute(context, BulkEditDocumentsRoute(BulkEditExtraWrapper(
selection: state.selection); state.selection,
LabelType.documentType,
)).push(context);
}, },
).paddedOnly(left: 8, right: 4), ).paddedOnly(left: 8, right: 4),
ActionChip( ActionChip(
label: Text(S.of(context)!.storagePath), label: Text(S.of(context)!.storagePath),
avatar: const Icon(Icons.edit), avatar: const Icon(Icons.edit),
onPressed: () async { onPressed: () async {
pushBulkEditStoragePathRoute(context, BulkEditDocumentsRoute(BulkEditExtraWrapper(
selection: state.selection); state.selection,
LabelType.storagePath,
)).push(context);
}, },
).paddedOnly(left: 8, right: 4), ).paddedOnly(left: 8, right: 4),
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4), _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
@@ -98,7 +105,10 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
label: Text(S.of(context)!.tags), label: Text(S.of(context)!.tags),
avatar: const Icon(Icons.edit), avatar: const Icon(Icons.edit),
onPressed: () { onPressed: () {
pushBulkEditTagsRoute(context, selection: state.selection); BulkEditDocumentsRoute(BulkEditExtraWrapper(
state.selection,
LabelType.tag,
)).push(context);
}, },
); );
} }

View File

@@ -2,6 +2,7 @@ import 'dart:developer';
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:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
@@ -119,11 +120,11 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
} catch (error, stackTrace) { } catch (error, stackTrace) {
log("An error occurred!", error: error, stackTrace: stackTrace); log("An error occurred!", error: error, stackTrace: stackTrace);
} }
Navigator.pop(context); context.pop();
} }
} else { } else {
onDelete(context, label); onDelete(context, label);
Navigator.pop(context); context.pop();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
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_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
@@ -74,6 +75,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
heroTag: "fab_label_form",
icon: widget.submitButtonConfig.icon, icon: widget.submitButtonConfig.icon,
label: widget.submitButtonConfig.label, label: widget.submitButtonConfig.label,
onPressed: _onSubmit, onPressed: _onSubmit,
@@ -168,7 +170,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
}; };
final parsed = widget.fromJsonT(mergedJson); final parsed = widget.fromJsonT(mergedJson);
final createdLabel = await widget.submitButtonConfig.onSubmit(parsed); final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
Navigator.pop(context, createdLabel); context.pop(createdLabel);
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} on PaperlessFormValidationException catch (exception) { } on PaperlessFormValidationException catch (exception) {

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
@@ -16,9 +17,14 @@ import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/home/view/model/api_version.dart'; import 'package:paperless_mobile/features/home/view/model/api_version.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart'; import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/routes/typed/branches/landing_route.dart';
import 'package:paperless_mobile/routes/typed/top_level/login_route.dart';
import 'package:paperless_mobile/routes/typed/top_level/switching_accounts_route.dart';
import 'package:paperless_mobile/routes/typed/top_level/verify_identity_route.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HomeShellWidget extends StatelessWidget { class HomeShellWidget extends StatelessWidget {
@@ -57,7 +63,10 @@ class HomeShellWidget extends StatelessWidget {
.listenable(keys: [currentUserId]), .listenable(keys: [currentUserId]),
builder: (context, box, _) { builder: (context, box, _) {
final currentLocalUser = box.get(currentUserId)!; final currentLocalUser = box.get(currentUserId)!;
print(currentLocalUser.paperlessUser.canViewDocuments);
print(currentLocalUser.paperlessUser.canViewTags);
return MultiProvider( return MultiProvider(
key: ValueKey(currentUserId),
providers: [ providers: [
Provider.value(value: currentLocalUser), Provider.value(value: currentLocalUser),
Provider.value(value: apiVersion), Provider.value(value: apiVersion),
@@ -162,15 +171,21 @@ class HomeShellWidget extends StatelessWidget {
create: (context) => create: (context) =>
DocumentScannerCubit(context.read()), DocumentScannerCubit(context.read()),
), ),
if (currentLocalUser.paperlessUser.canViewDocuments &&
currentLocalUser.paperlessUser.canViewTags)
Provider( Provider(
create: (context) => InboxCubit( create: (context) {
final inboxCubit = InboxCubit(
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
).initialize(), );
if (currentLocalUser
.paperlessUser.canViewDocuments &&
currentLocalUser.paperlessUser.canViewTags) {
inboxCubit.initialize();
}
return inboxCubit;
},
), ),
Provider( Provider(
create: (context) => SavedViewCubit( create: (context) => SavedViewCubit(

View File

@@ -46,24 +46,24 @@ class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
if (widget.authenticatedUser.canViewDocuments) { if (widget.authenticatedUser.canViewDocuments) {
widget.navigationShell.goBranch(index); widget.navigationShell.goBranch(index);
} else { } else {
showSnackBar( showSnackBar(context,
context, "You do not have permission to access this page."); "You do not have the required permissions to access this page.");
} }
break; break;
case _scannerIndex: case _scannerIndex:
if (widget.authenticatedUser.canCreateDocuments) { if (widget.authenticatedUser.canCreateDocuments) {
widget.navigationShell.goBranch(index); widget.navigationShell.goBranch(index);
} else { } else {
showSnackBar( showSnackBar(context,
context, "You do not have permission to access this page."); "You do not have the required permissions to access this page.");
} }
break; break;
case _labelsIndex: case _labelsIndex:
if (widget.authenticatedUser.canViewAnyLabel) { if (widget.authenticatedUser.canViewAnyLabel) {
widget.navigationShell.goBranch(index); widget.navigationShell.goBranch(index);
} else { } else {
showSnackBar( showSnackBar(context,
context, "You do not have permission to access this page."); "You do not have the required permissions to access this page.");
} }
break; break;
case _inboxIndex: case _inboxIndex:
@@ -71,8 +71,8 @@ class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
widget.authenticatedUser.canViewTags) { widget.authenticatedUser.canViewTags) {
widget.navigationShell.goBranch(index); widget.navigationShell.goBranch(index);
} else { } else {
showSnackBar( showSnackBar(context,
context, "You do not have permission to access this page."); "You do not have the required permissions to access this page.");
} }
break; break;
default: default:
@@ -132,7 +132,7 @@ class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
if (!(widget.authenticatedUser.canViewDocuments && if (!(widget.authenticatedUser.canViewDocuments &&
widget.authenticatedUser.canViewTags)) { widget.authenticatedUser.canViewTags)) {
return Icon( return Icon(
Icons.close, Icons.inbox_outlined,
color: disabledColor, color: disabledColor,
); );
} }

View File

@@ -48,6 +48,7 @@ class _InboxPageState extends State<InboxPage>
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return FloatingActionButton.extended( return FloatingActionButton.extended(
heroTag: "fab_inbox",
label: Text(S.of(context)!.allSeen), label: Text(S.of(context)!.allSeen),
icon: const Icon(Icons.done_all), icon: const Icon(Icons.done_all),
onPressed: state.hasLoaded && state.documents.isNotEmpty onPressed: state.hasLoaded && state.documents.isNotEmpty

View File

@@ -72,6 +72,7 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
return Scaffold( return Scaffold(
floatingActionButton: widget.allowCreation floatingActionButton: widget.allowCreation
? FloatingActionButton( ? FloatingActionButton(
heroTag: "fab_tags_form",
onPressed: _onAddTag, onPressed: _onAddTag,
child: const Icon(Icons.add), child: const Icon(Icons.add),
) )

View File

@@ -66,15 +66,19 @@ class _LabelsPageState extends State<LabelsPage>
child: Scaffold( child: Scaffold(
drawer: const AppDrawer(), drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: "fab_labels_page",
onPressed: [ onPressed: [
if (user.canViewCorrespondents) if (user.canViewCorrespondents)
() => CreateLabelRoute<Correspondent>().push(context), () => CreateLabelRoute(LabelType.correspondent)
.push(context),
if (user.canViewDocumentTypes) if (user.canViewDocumentTypes)
() => CreateLabelRoute<DocumentType>().push(context), () => CreateLabelRoute(LabelType.documentType)
.push(context),
if (user.canViewTags) if (user.canViewTags)
() => CreateLabelRoute<Tag>().push(context), () => CreateLabelRoute(LabelType.tag).push(context),
if (user.canViewStoragePaths) if (user.canViewStoragePaths)
() => CreateLabelRoute<StoragePath>().push(context), () => CreateLabelRoute(LabelType.storagePath)
.push(context),
][_currentIndex], ][_currentIndex],
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),
@@ -247,7 +251,8 @@ class _LabelsPageState extends State<LabelsPage>
}, },
emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent, emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent,
emptyStateDescription: S.of(context)!.noCorrespondentsSetUp, emptyStateDescription: S.of(context)!.noCorrespondentsSetUp,
onAddNew: () => CreateLabelRoute<Correspondent>().push(context), onAddNew: () =>
CreateLabelRoute(LabelType.correspondent).push(context),
), ),
], ],
); );
@@ -274,7 +279,8 @@ class _LabelsPageState extends State<LabelsPage>
}, },
emptyStateActionButtonLabel: S.of(context)!.addNewDocumentType, emptyStateActionButtonLabel: S.of(context)!.addNewDocumentType,
emptyStateDescription: S.of(context)!.noDocumentTypesSetUp, emptyStateDescription: S.of(context)!.noDocumentTypesSetUp,
onAddNew: () => CreateLabelRoute<DocumentType>().push(context), onAddNew: () =>
CreateLabelRoute(LabelType.documentType).push(context),
), ),
], ],
); );
@@ -310,7 +316,7 @@ class _LabelsPageState extends State<LabelsPage>
), ),
emptyStateActionButtonLabel: S.of(context)!.addNewTag, emptyStateActionButtonLabel: S.of(context)!.addNewTag,
emptyStateDescription: S.of(context)!.noTagsSetUp, emptyStateDescription: S.of(context)!.noTagsSetUp,
onAddNew: () => CreateLabelRoute<Tag>().push(context), onAddNew: () => CreateLabelRoute(LabelType.tag).push(context),
), ),
], ],
); );
@@ -338,7 +344,8 @@ class _LabelsPageState extends State<LabelsPage>
contentBuilder: (path) => Text(path.path), contentBuilder: (path) => Text(path.path),
emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath, emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath,
emptyStateDescription: S.of(context)!.noStoragePathsSetUp, emptyStateDescription: S.of(context)!.noStoragePathsSetUp,
onAddNew: () => CreateLabelRoute<StoragePath>().push(context), onAddNew: () =>
CreateLabelRoute(LabelType.storagePath).push(context),
), ),
], ],
); );

View File

@@ -4,6 +4,7 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/navigation/push_routes.dart'; import 'package:paperless_mobile/core/navigation/push_routes.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart'; import 'package:paperless_mobile/helpers/format_helpers.dart';
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
class LabelItem<T extends Label> extends StatelessWidget { class LabelItem<T extends Label> extends StatelessWidget {
final T label; final T label;
@@ -44,7 +45,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
onPressed: canOpen onPressed: canOpen
? () { ? () {
final filter = filterBuilder(label); final filter = filterBuilder(label);
pushLinkedDocumentsView(context, filter: filter); LinkedDocumentsRoute(filter).push(context);
} }
: null, : null,
); );

View File

@@ -8,7 +8,7 @@ class LabelText<T extends Label> extends StatelessWidget {
const LabelText({ const LabelText({
super.key, super.key,
this.style, this.style,
this.placeholder = "", this.placeholder = "-",
required this.label, required this.label,
}); });

View File

@@ -1,11 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:sliver_tools/sliver_tools.dart';
class LandingPage extends StatefulWidget { class LandingPage extends StatefulWidget {
const LandingPage({super.key}); const LandingPage({super.key});

View File

@@ -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:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
@@ -39,6 +40,7 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
title: Text(S.of(context)!.newView), title: Text(S.of(context)!.newView),
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
heroTag: "fab_add_saved_view_page",
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: () => _onCreate(context), onPressed: () => _onCreate(context),
label: Text(S.of(context)!.create), label: Text(S.of(context)!.create),
@@ -102,8 +104,7 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
void _onCreate(BuildContext context) { void _onCreate(BuildContext context) {
if (_savedViewFormKey.currentState?.saveAndValidate() ?? false) { if (_savedViewFormKey.currentState?.saveAndValidate() ?? false) {
Navigator.pop( context.pop(
context,
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
DocumentFilterForm.assembleFilter( DocumentFilterForm.assembleFilter(
_filterFormKey, _filterFormKey,

View File

@@ -47,7 +47,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
false; false;
if (shouldDelete) { if (shouldDelete) {
await widget.onDelete(cubit.savedView); await widget.onDelete(cubit.savedView);
Navigator.pop(context); context.pop(context);
} }
}, },
), ),

View File

@@ -37,6 +37,7 @@ import 'package:paperless_mobile/features/notifications/services/local_notificat
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/routes/navigation_keys.dart'; import 'package:paperless_mobile/routes/navigation_keys.dart';
import 'package:paperless_mobile/routes/routes.dart';
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart'; import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart';
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart'; import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
@@ -163,9 +164,7 @@ void main() async {
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit), BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
BlocProvider.value(value: authenticationCubit), BlocProvider.value(value: authenticationCubit),
], ],
child: GoRouterShell( child: GoRouterShell(apiFactory: apiFactory),
apiFactory: apiFactory,
),
), ),
), ),
); );
@@ -178,65 +177,9 @@ void main() async {
debugPrint("An unepxected exception has occured!"); debugPrint("An unepxected exception has occured!");
debugPrint(message); debugPrint(message);
debugPrintStack(stackTrace: stack); debugPrintStack(stackTrace: stack);
// if (_rootScaffoldKey.currentContext != null) {
// ScaffoldMessenger.maybeOf(_rootScaffoldKey.currentContext!)
// ?..hideCurrentSnackBar()
// ..showSnackBar(SnackBar(content: Text(message)));
// }
}); });
} }
// class PaperlessMobileEntrypoint extends StatefulWidget {
// final PaperlessApiFactory paperlessProviderFactory;
// const PaperlessMobileEntrypoint({
// Key? key,
// required this.paperlessProviderFactory,
// }) : super(key: key);
// @override
// State<PaperlessMobileEntrypoint> createState() =>
// _PaperlessMobileEntrypointState();
// }
// class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
// @override
// Widget build(BuildContext context) {
// return GlobalSettingsBuilder(
// builder: (context, settings) {
// return DynamicColorBuilder(
// builder: (lightDynamic, darkDynamic) {
// return MaterialApp(
// debugShowCheckedModeBanner: true,
// title: "Paperless Mobile",
// theme: buildTheme(
// brightness: Brightness.light,
// dynamicScheme: lightDynamic,
// preferredColorScheme: settings.preferredColorSchemeOption,
// ),
// darkTheme: buildTheme(
// brightness: Brightness.dark,
// dynamicScheme: darkDynamic,
// preferredColorScheme: settings.preferredColorSchemeOption,
// ),
// themeMode: settings.preferredThemeMode,
// supportedLocales: S.supportedLocales,
// locale: Locale.fromSubtags(
// languageCode: settings.preferredLocaleSubtag,
// ),
// localizationsDelegates: const [
// ...S.localizationsDelegates,
// ],
// home: AuthenticationWrapper(
// paperlessProviderFactory: widget.paperlessProviderFactory,
// ),
// );
// },
// );
// },
// );
// }
// }
class GoRouterShell extends StatefulWidget { class GoRouterShell extends StatefulWidget {
final PaperlessApiFactory apiFactory; final PaperlessApiFactory apiFactory;
const GoRouterShell({ const GoRouterShell({
@@ -252,21 +195,19 @@ class _GoRouterShellState extends State<GoRouterShell> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
context.read<AuthenticationCubit>().restoreSessionState().then((value) {
FlutterNativeSplash.remove();
});
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Activate the highest supported refresh rate on the device FlutterNativeSplash.remove();
if (Platform.isAndroid) { if (Platform.isAndroid) {
_setOptimalDisplayMode(); _setOptimalDisplayMode();
} }
initializeDateFormatting(); initializeDateFormatting();
} }
/// Activates the highest supported refresh rate on the device.
Future<void> _setOptimalDisplayMode() async { Future<void> _setOptimalDisplayMode() async {
final List<DisplayMode> supported = await FlutterDisplayMode.supported; final List<DisplayMode> supported = await FlutterDisplayMode.supported;
final DisplayMode active = await FlutterDisplayMode.active; final DisplayMode active = await FlutterDisplayMode.active;
@@ -284,7 +225,7 @@ class _GoRouterShellState extends State<GoRouterShell> {
} }
late final _router = GoRouter( late final _router = GoRouter(
debugLogDiagnostics: true, debugLogDiagnostics: kDebugMode,
initialLocation: "/login", initialLocation: "/login",
routes: [ routes: [
$loginRoute, $loginRoute,
@@ -348,23 +289,20 @@ class _GoRouterShellState extends State<GoRouterShell> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GlobalSettingsBuilder(
builder: (context, settings) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return BlocListener<AuthenticationCubit, AuthenticationState>( return BlocListener<AuthenticationCubit, AuthenticationState>(
listener: (context, state) { listener: (context, state) {
state.when( state.when(
unauthenticated: () => const LoginRoute().go(context), unauthenticated: () => _router.goNamed(R.login),
requriresLocalAuthentication: () => requriresLocalAuthentication: () => _router.goNamed(R.verifyIdentity),
const VerifyIdentityRoute().go(context), switchingAccounts: () => _router.goNamed(R.switchingAccounts),
authenticated: (localUserId) => authenticated: (localUserId) => _router.goNamed(R.landing),
const LandingRoute().go(context),
switchingAccounts: () =>
const SwitchingAccountsRoute().go(context),
); );
}, },
child: MaterialApp.router( child: GlobalSettingsBuilder(
builder: (context, settings) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return MaterialApp.router(
routerConfig: _router, routerConfig: _router,
debugShowCheckedModeBanner: true, debugShowCheckedModeBanner: true,
title: "Paperless Mobile", title: "Paperless Mobile",
@@ -384,79 +322,11 @@ class _GoRouterShellState extends State<GoRouterShell> {
languageCode: settings.preferredLocaleSubtag, languageCode: settings.preferredLocaleSubtag,
), ),
localizationsDelegates: S.localizationsDelegates, localizationsDelegates: S.localizationsDelegates,
);
},
);
},
), ),
); );
},
);
},
);
} }
} }
// class AuthenticationWrapper extends StatefulWidget {
// final PaperlessApiFactory paperlessProviderFactory;
// const AuthenticationWrapper({
// Key? key,
// required this.paperlessProviderFactory,
// }) : super(key: key);
// @override
// State<AuthenticationWrapper> createState() => _AuthenticationWrapperState();
// }
// class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// context.read<AuthenticationCubit>().restoreSessionState().then((value) {
// FlutterNativeSplash.remove();
// });
// }
// @override
// void initState() {
// super.initState();
// // Activate the highest supported refresh rate on the device
// if (Platform.isAndroid) {
// _setOptimalDisplayMode();
// }
// initializeDateFormatting();
// }
// Future<void> _setOptimalDisplayMode() async {
// final List<DisplayMode> supported = await FlutterDisplayMode.supported;
// final DisplayMode active = await FlutterDisplayMode.active;
// final List<DisplayMode> sameResolution = supported
// .where((m) => m.width == active.width && m.height == active.height)
// .toList()
// ..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
// final DisplayMode mostOptimalMode =
// sameResolution.isNotEmpty ? sameResolution.first : active;
// debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
// await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
// }
// @override
// Widget build(BuildContext context) {
// return BlocBuilder<AuthenticationCubit, AuthenticationState>(
// builder: (context, authentication) {
// return authentication.when(
// unauthenticated: () => const LoginPage(),
// requriresLocalAuthentication: () => const VerifyIdentityPage(),
// authenticated: (localUserId, apiVersion) => HomeShellWidget(
// key: ValueKey(localUserId),
// paperlessApiVersion: apiVersion,
// paperlessProviderFactory: widget.paperlessProviderFactory,
// localUserId: localUserId,
// ),
// switchingAccounts: () => const SwitchingAccountsPage(),
// );
// },
// );
// }
// }

View File

@@ -17,4 +17,6 @@ class R {
static const inbox = "inbox"; static const inbox = "inbox";
static const documentPreview = "documentPreview"; static const documentPreview = "documentPreview";
static const settings = "settings"; static const settings = "settings";
static const linkedDocuments = "linkedDocuments";
static const bulkEditDocuments = "bulkEditDocuments";
} }

View File

@@ -1,17 +1,17 @@
import 'dart:ffi';
import 'dart:typed_data';
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:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:flutter/widgets.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_label_page.dart';
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart'; import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart'; import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/routes/navigation_keys.dart'; import 'package:paperless_mobile/routes/navigation_keys.dart';
import 'package:paperless_mobile/routes/routes.dart'; import 'package:paperless_mobile/routes/routes.dart';
@@ -37,7 +37,11 @@ class DocumentsBranch extends StatefulShellBranchData {
TypedGoRoute<DocumentPreviewRoute>( TypedGoRoute<DocumentPreviewRoute>(
path: "preview", path: "preview",
name: R.documentPreview, name: R.documentPreview,
) ),
TypedGoRoute<BulkEditDocumentsRoute>(
path: "bulk-edit",
name: R.bulkEditDocuments,
),
], ],
) )
class DocumentsRoute extends GoRouteData { class DocumentsRoute extends GoRouteData {
@@ -101,13 +105,115 @@ class EditDocumentRoute extends GoRouteData {
} }
class DocumentPreviewRoute extends GoRouteData { class DocumentPreviewRoute extends GoRouteData {
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
final DocumentModel $extra; final DocumentModel $extra;
const DocumentPreviewRoute(this.$extra); final String? title;
const DocumentPreviewRoute({
required this.$extra,
this.title,
});
@override @override
Widget build(BuildContext context, GoRouterState state) { Widget build(BuildContext context, GoRouterState state) {
return DocumentView( return DocumentView(
documentBytes: context.read<PaperlessDocumentsApi>().download($extra), documentBytes: context.read<PaperlessDocumentsApi>().download($extra),
title: title ?? $extra.title,
);
}
}
class BulkEditExtraWrapper {
final List<DocumentModel> selection;
final LabelType type;
const BulkEditExtraWrapper(this.selection, this.type);
}
class BulkEditDocumentsRoute extends GoRouteData {
/// Selection
final BulkEditExtraWrapper $extra;
BulkEditDocumentsRoute(this.$extra);
@override
Widget build(BuildContext context, GoRouterState state) {
return BlocProvider(
create: (_) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
selection: $extra.selection,
),
child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
builder: (context, state) {
return switch ($extra.type) {
LabelType.tag => const FullscreenBulkEditTagsWidget(),
_ => FullscreenBulkEditLabelPage(
options: switch ($extra.type) {
LabelType.correspondent => state.correspondents,
LabelType.documentType => state.documentTypes,
LabelType.storagePath => state.storagePaths,
_ => throw Exception("Parameter not allowed here."),
},
selection: state.selection,
labelMapper: (document) {
return switch ($extra.type) {
LabelType.correspondent => document.correspondent,
LabelType.documentType => document.documentType,
LabelType.storagePath => document.storagePath,
_ => throw Exception("Parameter not allowed here."),
};
},
leadingIcon: switch ($extra.type) {
LabelType.correspondent => const Icon(Icons.person_outline),
LabelType.documentType =>
const Icon(Icons.description_outlined),
LabelType.storagePath => const Icon(Icons.folder_outlined),
_ => throw Exception("Parameter not allowed here."),
},
hintText: S.of(context)!.startTyping,
onSubmit: switch ($extra.type) {
LabelType.correspondent => context
.read<DocumentBulkActionCubit>()
.bulkModifyCorrespondent,
LabelType.documentType => context
.read<DocumentBulkActionCubit>()
.bulkModifyDocumentType,
LabelType.storagePath => context
.read<DocumentBulkActionCubit>()
.bulkModifyStoragePath,
_ => throw Exception("Parameter not allowed here."),
},
assignMessageBuilder: (int count, String name) {
return switch ($extra.type) {
LabelType.correspondent => S
.of(context)!
.bulkEditCorrespondentAssignMessage(name, count),
LabelType.documentType => S
.of(context)!
.bulkEditDocumentTypeAssignMessage(count, name),
LabelType.storagePath => S
.of(context)!
.bulkEditDocumentTypeAssignMessage(count, name),
_ => throw Exception("Parameter not allowed here."),
};
},
removeMessageBuilder: (int count) {
return switch ($extra.type) {
LabelType.correspondent =>
S.of(context)!.bulkEditCorrespondentRemoveMessage(count),
LabelType.documentType =>
S.of(context)!.bulkEditDocumentTypeRemoveMessage(count),
LabelType.storagePath =>
S.of(context)!.bulkEditStoragePathRemoveMessage(count),
_ => throw Exception("Parameter not allowed here."),
};
},
),
};
},
),
); );
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
@@ -10,6 +11,8 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_typ
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart'; import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
import 'package:paperless_mobile/routes/navigation_keys.dart'; import 'package:paperless_mobile/routes/navigation_keys.dart';
import 'package:paperless_mobile/routes/routes.dart'; import 'package:paperless_mobile/routes/routes.dart';
@@ -32,6 +35,10 @@ class LabelsBranch extends StatefulShellBranchData {
path: "create", path: "create",
name: R.createLabel, name: R.createLabel,
), ),
TypedGoRoute<LinkedDocumentsRoute>(
path: "linked-documents",
name: R.linkedDocuments,
),
], ],
) )
class LabelsRoute extends GoRouteData { class LabelsRoute extends GoRouteData {
@@ -59,26 +66,42 @@ class EditLabelRoute extends GoRouteData {
} }
} }
class CreateLabelRoute<T extends Label> extends GoRouteData { class CreateLabelRoute extends GoRouteData {
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey; static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
final LabelType $extra;
final String? name; final String? name;
CreateLabelRoute({ CreateLabelRoute(
this.$extra, {
this.name, this.name,
}); });
@override @override
Widget build(BuildContext context, GoRouterState state) { Widget build(BuildContext context, GoRouterState state) {
if (T is Correspondent) { return switch ($extra) {
return AddCorrespondentPage(initialName: name); LabelType.correspondent => AddCorrespondentPage(initialName: name),
} else if (T is DocumentType) { LabelType.documentType => AddDocumentTypePage(initialName: name),
return AddDocumentTypePage(initialName: name); LabelType.tag => AddTagPage(initialName: name),
} else if (T is Tag) { LabelType.storagePath => AddStoragePathPage(initialName: name),
return AddTagPage(initialName: name); };
} else if (T is StoragePath) { }
return AddStoragePathPage(initialName: name); }
}
throw ArgumentError(); class LinkedDocumentsRoute extends GoRouteData {
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
final DocumentFilter $extra;
const LinkedDocumentsRoute(this.$extra);
@override
Widget build(BuildContext context, GoRouterState state) {
return BlocProvider(
create: (context) => LinkedDocumentsCubit(
$extra,
context.read(),
context.read(),
context.read(),
),
child: const LinkedDocumentsPage(),
);
} }
} }

View File

@@ -8,6 +8,13 @@ import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
part 'label_model.g.dart'; part 'label_model.g.dart';
enum LabelType {
correspondent,
documentType,
tag,
storagePath,
}
sealed class Label extends Equatable implements Comparable { sealed class Label extends Equatable implements Comparable {
static const idKey = "id"; static const idKey = "id";
static const nameKey = "name"; static const nameKey = "name";

View File

@@ -124,19 +124,22 @@ class _ScanState extends State<Scan> {
imagePath = filePath; imagePath = filePath;
}); });
EdgeDetectionResult result = await EdgeDetector().detectEdgesFromFile(filePath); EdgeDetectionResult result =
await EdgeDetector().detectEdgesFromFile(filePath);
setState(() { setState(() {
edgeDetectionResult = result; edgeDetectionResult = result;
}); });
} }
Future _processImage(String filePath, EdgeDetectionResult edgeDetectionResult) async { Future _processImage(
String filePath, EdgeDetectionResult edgeDetectionResult) async {
if (!mounted) { if (!mounted) {
return; return;
} }
bool result = await EdgeDetector().processImageFromFile(filePath, edgeDetectionResult); bool result = await EdgeDetector()
.processImageFromFile(filePath, edgeDetectionResult);
if (result == false) { if (result == false) {
return; return;