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_bloc/flutter_bloc.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_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_app_state.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/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_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/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/view/add_saved_view_page.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: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(
BuildContext context, {
required SavedView savedView,
@@ -84,7 +47,8 @@ Future<void> pushSavedViewDetailsRoute(
savedView: savedView,
),
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(
BuildContext context, {
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) {
return [
Provider.value(value: context.read<PaperlessDocumentsApi>()),

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
@@ -44,6 +45,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
@override
Widget build(BuildContext context) {
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
builder: (context, state) {
final filteredSuggestions = state.suggestions?.documentDifference(
@@ -53,6 +55,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton.extended(
heroTag: "fab_document_edit",
onPressed: () => _onSubmit(state.document),
icon: const Icon(Icons.save),
label: Text(S.of(context)!.saveChanges),
@@ -90,6 +93,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
filteredSuggestions,
).padded(),
// Correspondent form field
if (currentUser.canViewCorrespondents)
Column(
children: [
LabelFormField<Correspondent>(
@@ -116,10 +120,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined),
allowSelectUnassigned: true,
canCreateNewLabel: context
.watch<LocalUserAccount>()
.paperlessUser
.canCreateCorrespondents,
canCreateNewLabel:
currentUser.canCreateCorrespondents,
),
if (filteredSuggestions
?.hasSuggestedCorrespondents ??
@@ -132,8 +134,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
label: Text(
state.correspondents[itemData]!.name),
onPressed: () {
_formKey
.currentState?.fields[fkCorrespondent]
_formKey.currentState
?.fields[fkCorrespondent]
?.didChange(
IdQueryParameter.fromId(itemData),
);
@@ -143,6 +145,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
],
).padded(),
// DocumentType form field
if (currentUser.canViewDocumentTypes)
Column(
children: [
LabelFormField<DocumentType>(
@@ -155,10 +158,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
initialName: currentInput,
),
),
canCreateNewLabel: context
.watch<LocalUserAccount>()
.paperlessUser
.canCreateDocumentTypes,
canCreateNewLabel:
currentUser.canCreateDocumentTypes,
addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType,
initialValue:
@@ -192,6 +193,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
],
).padded(),
// StoragePath form field
if (currentUser.canViewStoragePaths)
Column(
children: [
LabelFormField<StoragePath>(
@@ -203,14 +205,13 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddStoragePathPage(
initialName: initialValue),
),
canCreateNewLabel: context
.watch<LocalUserAccount>()
.paperlessUser
.canCreateStoragePaths,
canCreateNewLabel:
currentUser.canCreateStoragePaths,
addLabelText: S.of(context)!.addStoragePath,
labelText: S.of(context)!.storagePath,
options: state.storagePaths,
initialValue: state.document.storagePath != null
initialValue:
state.document.storagePath != null
? IdQueryParameter.fromId(
state.document.storagePath!)
: const IdQueryParameter.unset(),
@@ -221,6 +222,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
],
).padded(),
// Tag form field
if (currentUser.canViewTags)
TagsFormField(
options: state.tags,
name: fkTags,
@@ -321,7 +323,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
setState(() {
_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/scanned_image_item.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/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_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:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
@@ -53,18 +55,13 @@ class _ScannerPageState extends State<ScannerPage>
Widget build(BuildContext context) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectedState) {
return Scaffold(
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton(
onPressed: () => _openDocumentScanner(context),
child: const Icon(Icons.add_a_photo_outlined),
),
body: BlocBuilder<DocumentScannerCubit, List<File>>(
return BlocBuilder<DocumentScannerCubit, List<File>>(
builder: (context, state) {
return SafeArea(
child: Scaffold(
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton(
heroTag: "fab_document_edit",
onPressed: () => _openDocumentScanner(context),
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()!
.enforceSinglePagePdfUpload,
);
final uploadResult = await pushDocumentUploadPreparationPage(
context,
bytes: file.bytes,
final uploadResult = await DocumentUploadRoute(
$extra: file.bytes,
fileExtension: file.extension,
);
).push<DocumentUploadResult>(context);
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
// For paperless version older than 1.11.3, task id will always be null!
context.read<DocumentScannerCubit>().reset();
@@ -366,13 +361,12 @@ class _ScannerPageState extends State<ScannerPage>
);
return;
}
pushDocumentUploadPreparationPage(
context,
bytes: file.readAsBytesSync(),
DocumentUploadRoute(
$extra: file.readAsBytesSync(),
filename: fileDescription.filename,
title: fileDescription.filename,
fileExtension: fileDescription.extension,
);
).push(context);
}
}

View File

@@ -1,18 +1,12 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.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/database/tables/local_user_account.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/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/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart';
@@ -85,24 +79,14 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
);
},
openBuilder: (_, action) {
return MultiProvider(
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(
return Provider(
create: (_) => DocumentSearchCubit(
context.read(),
context.read(),
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
.get(context.watch<LocalUserAccount>().id)!,
),
builder: (_, __) => const DocumentSearchPage(),
.get(context.read<LocalUserAccount>().id)!,
),
child: const DocumentSearchPage(),
);
},
),
@@ -114,11 +98,10 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
padding: const EdgeInsets.all(6),
icon: UserAvatar(account: context.watch<LocalUserAccount>()),
onPressed: () {
final apiVersion = context.read<ApiVersion>();
showDialog(
context: context,
builder: (context) => Provider.value(
value: apiVersion,
builder: (_) => Provider.value(
value: context.read<LocalUserAccount>(),
child: const ManageAccountsPage(),
),
);

View File

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

View File

@@ -3,8 +3,9 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:go_router/go_router.dart';
import 'package:hive/hive.dart';
import 'package:image/image.dart' as img;
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
@@ -56,7 +57,7 @@ class _DocumentUploadPreparationPageState
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
Color? _titleColor;
Map<String, String> _errors = {};
bool _isUploadLoading = false;
late bool _syncTitleAndFilename;
@@ -67,24 +68,21 @@ class _DocumentUploadPreparationPageState
void initState() {
super.initState();
_syncTitleAndFilename = widget.filename == null && widget.title == null;
_titleColor = _computeAverageColor().computeLuminance() > 0.5
? Colors.black
: Colors.white;
initializeDateFormatting();
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: false,
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text(S.of(context)!.prepareDocument),
bottom: _isUploadLoading
? const PreferredSize(
child: LinearProgressIndicator(),
preferredSize: Size.fromHeight(4.0))
: null,
),
floatingActionButton: Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0,
child: FloatingActionButton.extended(
heroTag: "fab_document_upload",
onPressed: _onSubmit,
label: Text(S.of(context)!.upload),
icon: const Icon(Icons.upload),
@@ -94,14 +92,57 @@ class _DocumentUploadPreparationPageState
builder: (context, state) {
return FormBuilder(
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: [
// Title
FormBuilderTextField(
autovalidateMode: AutovalidateMode.always,
name: DocumentModel.titleKey,
initialValue:
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
initialValue: widget.title ??
"scan_${fileNameDateFormat.format(_now)}",
validator: (value) {
if (value?.trim().isEmpty ?? true) {
return S.of(context)!.thisFieldIsRequired;
@@ -113,7 +154,8 @@ class _DocumentUploadPreparationPageState
suffixIcon: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_formKey.currentState?.fields[DocumentModel.titleKey]
_formKey.currentState
?.fields[DocumentModel.titleKey]
?.didChange("");
if (_syncTitleAndFilename) {
_formKey.currentState?.fields[fkFileName]
@@ -143,7 +185,8 @@ class _DocumentUploadPreparationPageState
suffixText: widget.fileExtension,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkFileName]
onPressed: () => _formKey
.currentState?.fields[fkFileName]
?.didChange(''),
),
),
@@ -158,7 +201,8 @@ class _DocumentUploadPreparationPageState
() => _syncTitleAndFilename = value,
);
if (_syncTitleAndFilename) {
final String transformedValue = _formatFilename(_formKey
final String transformedValue =
_formatFilename(_formKey
.currentState
?.fields[DocumentModel.titleKey]
?.value as String);
@@ -180,10 +224,12 @@ class _DocumentUploadPreparationPageState
name: DocumentModel.createdKey,
initialValue: null,
onChanged: (value) {
setState(() => _showDatePickerDeleteIcon = value != null);
setState(() =>
_showDatePickerDeleteIcon = value != null);
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month_outlined),
prefixIcon:
const Icon(Icons.calendar_month_outlined),
labelText: S.of(context)!.createdAt + " *",
suffixIcon: _showDatePickerDeleteIcon
? IconButton(
@@ -205,7 +251,8 @@ class _DocumentUploadPreparationPageState
LabelFormField<Correspondent>(
showAnyAssignedOption: false,
showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => MultiProvider(
addLabelPageBuilder: (initialName) =>
MultiProvider(
providers: [
Provider.value(
value: context.read<LabelRepository>(),
@@ -214,7 +261,8 @@ class _DocumentUploadPreparationPageState
value: context.read<ApiVersion>(),
)
],
child: AddCorrespondentPage(initialName: initialName),
child: AddCorrespondentPage(
initialName: initialName),
),
addLabelText: S.of(context)!.addCorrespondent,
labelText: S.of(context)!.correspondent + " *",
@@ -235,7 +283,8 @@ class _DocumentUploadPreparationPageState
LabelFormField<DocumentType>(
showAnyAssignedOption: false,
showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => MultiProvider(
addLabelPageBuilder: (initialName) =>
MultiProvider(
providers: [
Provider.value(
value: context.read<LabelRepository>(),
@@ -244,20 +293,25 @@ class _DocumentUploadPreparationPageState
value: context.read<ApiVersion>(),
)
],
child: AddDocumentTypePage(initialName: initialName),
child: AddDocumentTypePage(
initialName: initialName),
),
addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType + " *",
name: DocumentModel.documentTypeKey,
options: state.documentTypes,
prefixIcon: const Icon(Icons.description_outlined),
prefixIcon:
const Icon(Icons.description_outlined),
allowSelectUnassigned: true,
canCreateNewLabel: context
.watch<LocalUserAccount>()
.paperlessUser
.canCreateDocumentTypes,
),
if (context.watch<LocalUserAccount>().paperlessUser.canViewTags)
if (context
.watch<LocalUserAccount>()
.paperlessUser
.canViewTags)
TagsFormField(
name: DocumentModel.tagsKey,
allowCreation: true,
@@ -268,10 +322,17 @@ class _DocumentUploadPreparationPageState
Text(
"* " + S.of(context)!.uploadInferValuesHint,
style: Theme.of(context).textTheme.bodySmall,
),
textAlign: TextAlign.justify,
).padded(),
const SizedBox(height: 300),
].padded(),
),
],
);
},
),
),
),
);
},
),
@@ -317,10 +378,7 @@ class _DocumentUploadPreparationPageState
context,
S.of(context)!.documentSuccessfullyUploadedProcessing,
);
Navigator.pop(
context,
DocumentUploadResult(true, taskId),
);
context.pop(DocumentUploadResult(true, taskId));
} on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
} on PaperlessFormValidationException catch (exception) {
@@ -345,4 +403,33 @@ class _DocumentUploadPreparationPageState
String _formatFilename(String source) {
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: const EdgeInsets.all(8.0),
child: FloatingActionButton.small(
key: UniqueKey(),
heroTag: "fab_documents_page_reset_filter",
backgroundColor: Theme.of(context)
.colorScheme
.onPrimaryContainer,
@@ -164,11 +164,13 @@ class _DocumentsPageState extends State<DocumentsPage>
duration: const Duration(milliseconds: 250),
child: (_currentTab == 0)
? FloatingActionButton(
heroTag: "fab_documents_page_filter",
child:
const Icon(Icons.filter_alt_outlined),
onPressed: _openDocumentFilter,
)
: FloatingActionButton(
heroTag: "fab_documents_page_filter",
child: const Icon(Icons.add),
onPressed: () =>
_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_cache_manager/flutter_cache_manager.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:shimmer/shimmer.dart';
@@ -12,6 +13,7 @@ class DocumentPreview extends StatelessWidget {
final double borderRadius;
final bool enableHero;
final double scale;
final bool isClickable;
const DocumentPreview({
super.key,
@@ -21,16 +23,24 @@ class DocumentPreview extends StatelessWidget {
this.borderRadius = 12.0,
this.enableHero = true,
this.scale = 1.1,
this.isClickable = true,
});
@override
Widget build(BuildContext context) {
return HeroMode(
return GestureDetector(
onTap: isClickable
? () {
DocumentPreviewRoute($extra: document).push(context);
}
: null,
child: HeroMode(
enabled: enableHero,
child: Hero(
tag: "thumb_${document.id}",
child: _buildPreview(context),
),
),
);
}

View File

@@ -80,6 +80,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
floatingActionButton: Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0,
child: FloatingActionButton.extended(
heroTag: "fab_document_filter_panel",
icon: const Icon(Icons.done),
label: Text(S.of(context)!.apply),
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/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
class DocumentSelectionSliverAppBar extends StatelessWidget {
final DocumentsState state;
@@ -65,24 +66,30 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
label: Text(S.of(context)!.correspondent),
avatar: const Icon(Icons.edit),
onPressed: () {
pushBulkEditCorrespondentRoute(context,
selection: state.selection);
BulkEditDocumentsRoute(BulkEditExtraWrapper(
state.selection,
LabelType.correspondent,
)).push(context);
},
).paddedOnly(left: 8, right: 4),
ActionChip(
label: Text(S.of(context)!.documentType),
avatar: const Icon(Icons.edit),
onPressed: () async {
pushBulkEditDocumentTypeRoute(context,
selection: state.selection);
BulkEditDocumentsRoute(BulkEditExtraWrapper(
state.selection,
LabelType.documentType,
)).push(context);
},
).paddedOnly(left: 8, right: 4),
ActionChip(
label: Text(S.of(context)!.storagePath),
avatar: const Icon(Icons.edit),
onPressed: () async {
pushBulkEditStoragePathRoute(context,
selection: state.selection);
BulkEditDocumentsRoute(BulkEditExtraWrapper(
state.selection,
LabelType.storagePath,
)).push(context);
},
).paddedOnly(left: 8, right: 4),
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
@@ -98,7 +105,10 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
label: Text(S.of(context)!.tags),
avatar: const Icon(Icons.edit),
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_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.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) {
log("An error occurred!", error: error, stackTrace: stackTrace);
}
Navigator.pop(context);
context.pop();
}
} else {
onDelete(context, label);
Navigator.pop(context);
context.pop();
}
}
}

View File

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

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:hive_flutter/adapters.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/inbox/cubit/inbox_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/settings/view/widgets/global_settings_builder.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';
class HomeShellWidget extends StatelessWidget {
@@ -57,7 +63,10 @@ class HomeShellWidget extends StatelessWidget {
.listenable(keys: [currentUserId]),
builder: (context, box, _) {
final currentLocalUser = box.get(currentUserId)!;
print(currentLocalUser.paperlessUser.canViewDocuments);
print(currentLocalUser.paperlessUser.canViewTags);
return MultiProvider(
key: ValueKey(currentUserId),
providers: [
Provider.value(value: currentLocalUser),
Provider.value(value: apiVersion),
@@ -162,15 +171,21 @@ class HomeShellWidget extends StatelessWidget {
create: (context) =>
DocumentScannerCubit(context.read()),
),
if (currentLocalUser.paperlessUser.canViewDocuments &&
currentLocalUser.paperlessUser.canViewTags)
Provider(
create: (context) => InboxCubit(
create: (context) {
final inboxCubit = InboxCubit(
context.read(),
context.read(),
context.read(),
context.read(),
).initialize(),
);
if (currentLocalUser
.paperlessUser.canViewDocuments &&
currentLocalUser.paperlessUser.canViewTags) {
inboxCubit.initialize();
}
return inboxCubit;
},
),
Provider(
create: (context) => SavedViewCubit(

View File

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

View File

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

View File

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

View File

@@ -66,15 +66,19 @@ class _LabelsPageState extends State<LabelsPage>
child: Scaffold(
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton(
heroTag: "fab_labels_page",
onPressed: [
if (user.canViewCorrespondents)
() => CreateLabelRoute<Correspondent>().push(context),
() => CreateLabelRoute(LabelType.correspondent)
.push(context),
if (user.canViewDocumentTypes)
() => CreateLabelRoute<DocumentType>().push(context),
() => CreateLabelRoute(LabelType.documentType)
.push(context),
if (user.canViewTags)
() => CreateLabelRoute<Tag>().push(context),
() => CreateLabelRoute(LabelType.tag).push(context),
if (user.canViewStoragePaths)
() => CreateLabelRoute<StoragePath>().push(context),
() => CreateLabelRoute(LabelType.storagePath)
.push(context),
][_currentIndex],
child: const Icon(Icons.add),
),
@@ -247,7 +251,8 @@ class _LabelsPageState extends State<LabelsPage>
},
emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent,
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,
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,
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),
emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath,
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/navigation/push_routes.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 {
final T label;
@@ -44,7 +45,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
onPressed: canOpen
? () {
final filter = filterBuilder(label);
pushLinkedDocumentsView(context, filter: filter);
LinkedDocumentsRoute(filter).push(context);
}
: null,
);

View File

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

View File

@@ -1,11 +1,7 @@
import 'package:flutter/foundation.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/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:sliver_tools/sliver_tools.dart';
class LandingPage extends StatefulWidget {
const LandingPage({super.key});

View File

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

View File

@@ -47,7 +47,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
false;
if (shouldDelete) {
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/generated/l10n/app_localizations.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/inbox_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.value(value: authenticationCubit),
],
child: GoRouterShell(
apiFactory: apiFactory,
),
child: GoRouterShell(apiFactory: apiFactory),
),
),
);
@@ -178,65 +177,9 @@ void main() async {
debugPrint("An unepxected exception has occured!");
debugPrint(message);
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 {
final PaperlessApiFactory apiFactory;
const GoRouterShell({
@@ -252,21 +195,19 @@ class _GoRouterShellState extends State<GoRouterShell> {
@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
FlutterNativeSplash.remove();
if (Platform.isAndroid) {
_setOptimalDisplayMode();
}
initializeDateFormatting();
}
/// Activates the highest supported refresh rate on the device.
Future<void> _setOptimalDisplayMode() async {
final List<DisplayMode> supported = await FlutterDisplayMode.supported;
final DisplayMode active = await FlutterDisplayMode.active;
@@ -284,7 +225,7 @@ class _GoRouterShellState extends State<GoRouterShell> {
}
late final _router = GoRouter(
debugLogDiagnostics: true,
debugLogDiagnostics: kDebugMode,
initialLocation: "/login",
routes: [
$loginRoute,
@@ -348,23 +289,20 @@ class _GoRouterShellState extends State<GoRouterShell> {
@override
Widget build(BuildContext context) {
return GlobalSettingsBuilder(
builder: (context, settings) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return BlocListener<AuthenticationCubit, AuthenticationState>(
listener: (context, state) {
state.when(
unauthenticated: () => const LoginRoute().go(context),
requriresLocalAuthentication: () =>
const VerifyIdentityRoute().go(context),
authenticated: (localUserId) =>
const LandingRoute().go(context),
switchingAccounts: () =>
const SwitchingAccountsRoute().go(context),
unauthenticated: () => _router.goNamed(R.login),
requriresLocalAuthentication: () => _router.goNamed(R.verifyIdentity),
switchingAccounts: () => _router.goNamed(R.switchingAccounts),
authenticated: (localUserId) => _router.goNamed(R.landing),
);
},
child: MaterialApp.router(
child: GlobalSettingsBuilder(
builder: (context, settings) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return MaterialApp.router(
routerConfig: _router,
debugShowCheckedModeBanner: true,
title: "Paperless Mobile",
@@ -384,79 +322,11 @@ class _GoRouterShellState extends State<GoRouterShell> {
languageCode: settings.preferredLocaleSubtag,
),
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 documentPreview = "documentPreview";
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_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter/widgets.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/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/view/document_edit_page.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/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/routes/navigation_keys.dart';
import 'package:paperless_mobile/routes/routes.dart';
@@ -37,7 +37,11 @@ class DocumentsBranch extends StatefulShellBranchData {
TypedGoRoute<DocumentPreviewRoute>(
path: "preview",
name: R.documentPreview,
)
),
TypedGoRoute<BulkEditDocumentsRoute>(
path: "bulk-edit",
name: R.bulkEditDocuments,
),
],
)
class DocumentsRoute extends GoRouteData {
@@ -101,13 +105,115 @@ class EditDocumentRoute extends GoRouteData {
}
class DocumentPreviewRoute extends GoRouteData {
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
final DocumentModel $extra;
const DocumentPreviewRoute(this.$extra);
final String? title;
const DocumentPreviewRoute({
required this.$extra,
this.title,
});
@override
Widget build(BuildContext context, GoRouterState state) {
return DocumentView(
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_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:paperless_api/paperless_api.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_tag_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/routes.dart';
@@ -32,6 +35,10 @@ class LabelsBranch extends StatefulShellBranchData {
path: "create",
name: R.createLabel,
),
TypedGoRoute<LinkedDocumentsRoute>(
path: "linked-documents",
name: R.linkedDocuments,
),
],
)
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;
final LabelType $extra;
final String? name;
CreateLabelRoute({
CreateLabelRoute(
this.$extra, {
this.name,
});
@override
Widget build(BuildContext context, GoRouterState state) {
if (T is Correspondent) {
return AddCorrespondentPage(initialName: name);
} else if (T is DocumentType) {
return AddDocumentTypePage(initialName: name);
} else if (T is Tag) {
return AddTagPage(initialName: name);
} else if (T is StoragePath) {
return AddStoragePathPage(initialName: name);
}
throw ArgumentError();
return switch ($extra) {
LabelType.correspondent => AddCorrespondentPage(initialName: name),
LabelType.documentType => AddDocumentTypePage(initialName: name),
LabelType.tag => AddTagPage(initialName: name),
LabelType.storagePath => AddStoragePathPage(initialName: name),
};
}
}
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';
enum LabelType {
correspondent,
documentType,
tag,
storagePath,
}
sealed class Label extends Equatable implements Comparable {
static const idKey = "id";
static const nameKey = "name";

View File

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