mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 01:15:44 -06:00
feat: Further migrations to go_router, add onclick to document previews
This commit is contained in:
@@ -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>()),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: DocumentPreview(
|
||||
document: state.document,
|
||||
fit: BoxFit.cover,
|
||||
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),
|
||||
title: document.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
DocumentPreviewRoute(
|
||||
$extra: document,
|
||||
title: document.title,
|
||||
).push(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 +39,10 @@ class _DocumentMetaDataWidgetState extends State<DocumentMetaDataWidget> {
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
ArchiveSerialNumberField(
|
||||
document: widget.document,
|
||||
).paddedOnly(bottom: widget.itemSpacing),
|
||||
if (currentUser.canEditDocuments)
|
||||
ArchiveSerialNumberField(
|
||||
document: widget.document,
|
||||
).paddedOnly(bottom: widget.itemSpacing),
|
||||
DetailsItem.text(
|
||||
DateFormat().format(widget.document.modified),
|
||||
context: context,
|
||||
|
||||
@@ -31,71 +31,66 @@ 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(
|
||||
text: document.title,
|
||||
highlights: queryString?.split(" ") ?? [],
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
DetailsItem.text(
|
||||
DateFormat.yMMMMd().format(document.created),
|
||||
context: context,
|
||||
label: S.of(context)!.createdAt,
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.documentType != null &&
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.title,
|
||||
content: HighlightedText(
|
||||
text: document.title,
|
||||
highlights: queryString?.split(" ") ?? [],
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableDocumentTypes[document.documentType],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
DetailsItem.text(
|
||||
DateFormat.yMMMMd().format(document.created),
|
||||
context: context,
|
||||
label: S.of(context)!.createdAt,
|
||||
if (document.correspondent != null &&
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableCorrespondents[document.correspondent],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.documentType != null &&
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableDocumentTypes[document.documentType],
|
||||
if (document.storagePath != null &&
|
||||
context.watch<LocalUserAccount>().paperlessUser.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
label: availableStoragePaths[document.storagePath],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.tags.isNotEmpty &&
|
||||
context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.tags,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: TagsWidget(
|
||||
isClickable: false,
|
||||
tags: document.tags.map((e) => availableTags[e]!).toList(),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.correspondent != null &&
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableCorrespondents[document.correspondent],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.storagePath != null &&
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
label: availableStoragePaths[document.storagePath],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.tags.isNotEmpty &&
|
||||
context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.tags,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: TagsWidget(
|
||||
isClickable: false,
|
||||
tags: document.tags.map((e) => availableTags[e]!).toList(),
|
||||
),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
],
|
||||
),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,147 +93,146 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
filteredSuggestions,
|
||||
).padded(),
|
||||
// Correspondent form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddCorrespondentPage(
|
||||
initialName: initialValue,
|
||||
),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
options: context
|
||||
.watch<DocumentEditCubit>()
|
||||
.state
|
||||
.correspondents,
|
||||
initialValue:
|
||||
state.document.correspondent != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.correspondent!)
|
||||
: const IdQueryParameter.unset(),
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
),
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedCorrespondents ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
filteredSuggestions!.correspondents,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.correspondents[itemData]!.name),
|
||||
onPressed: () {
|
||||
_formKey
|
||||
.currentState?.fields[fkCorrespondent]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
// DocumentType form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue:
|
||||
state.document.documentType != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.documentType!)
|
||||
: const IdQueryParameter.unset(),
|
||||
options: state.documentTypes,
|
||||
name: _DocumentEditPageState.fkDocumentType,
|
||||
prefixIcon:
|
||||
const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
),
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedDocumentTypes ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
filteredSuggestions!.documentTypes,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.documentTypes[itemData]!.name),
|
||||
onPressed: () => _formKey
|
||||
.currentState?.fields[fkDocumentType]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
if (currentUser.canViewCorrespondents)
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddCorrespondentPage(
|
||||
initialName: initialValue,
|
||||
),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
options: context
|
||||
.watch<DocumentEditCubit>()
|
||||
.state
|
||||
.correspondents,
|
||||
initialValue:
|
||||
state.document.correspondent != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.correspondent!)
|
||||
: const IdQueryParameter.unset(),
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel:
|
||||
currentUser.canCreateCorrespondents,
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedCorrespondents ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
filteredSuggestions!.correspondents,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.correspondents[itemData]!.name),
|
||||
onPressed: () {
|
||||
_formKey.currentState
|
||||
?.fields[fkCorrespondent]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
// DocumentType form field
|
||||
if (currentUser.canViewDocumentTypes)
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
canCreateNewLabel:
|
||||
currentUser.canCreateDocumentTypes,
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue:
|
||||
state.document.documentType != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.documentType!)
|
||||
: const IdQueryParameter.unset(),
|
||||
options: state.documentTypes,
|
||||
name: _DocumentEditPageState.fkDocumentType,
|
||||
prefixIcon:
|
||||
const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
),
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedDocumentTypes ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
filteredSuggestions!.documentTypes,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.documentTypes[itemData]!.name),
|
||||
onPressed: () => _formKey
|
||||
.currentState?.fields[fkDocumentType]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
// StoragePath form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<StoragePath>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddStoragePathPage(
|
||||
initialName: initialValue),
|
||||
if (currentUser.canViewStoragePaths)
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<StoragePath>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddStoragePathPage(
|
||||
initialName: initialValue),
|
||||
),
|
||||
canCreateNewLabel:
|
||||
currentUser.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options: state.storagePaths,
|
||||
initialValue:
|
||||
state.document.storagePath != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.storagePath!)
|
||||
: const IdQueryParameter.unset(),
|
||||
name: fkStoragePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
),
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options: state.storagePaths,
|
||||
initialValue: state.document.storagePath != null
|
||||
? IdQueryParameter.fromId(
|
||||
state.document.storagePath!)
|
||||
: const IdQueryParameter.unset(),
|
||||
name: fkStoragePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
],
|
||||
).padded(),
|
||||
// Tag form field
|
||||
TagsFormField(
|
||||
options: state.tags,
|
||||
name: fkTags,
|
||||
allowOnlySelection: true,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
initialValue: TagsQuery.ids(
|
||||
include: state.document.tags.toList(),
|
||||
),
|
||||
).padded(),
|
||||
if (currentUser.canViewTags)
|
||||
TagsFormField(
|
||||
options: state.tags,
|
||||
name: fkTags,
|
||||
allowOnlySelection: true,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
initialValue: TagsQuery.ids(
|
||||
include: state.document.tags.toList(),
|
||||
),
|
||||
).padded(),
|
||||
if (filteredSuggestions?.tags
|
||||
.toSet()
|
||||
.difference(state.document.tags.toSet())
|
||||
@@ -321,7 +323,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
setState(() {
|
||||
_isSubmitLoading = false;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,58 +55,52 @@ 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>>(
|
||||
builder: (context, state) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _openDocumentScanner(context),
|
||||
child: const Icon(Icons.add_a_photo_outlined),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.scanner,
|
||||
),
|
||||
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),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.scanner,
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: actionsHandle,
|
||||
sliver: SliverPinnedHeader(
|
||||
child: _buildActions(connectedState.isConnected),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
if (state.isEmpty) {
|
||||
return SizedBox.expand(
|
||||
child: Center(
|
||||
child: _buildEmptyState(
|
||||
connectedState.isConnected,
|
||||
state,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return _buildImageGrid(state);
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: actionsHandle,
|
||||
sliver: SliverPinnedHeader(
|
||||
child: _buildActions(connectedState.isConnected),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
if (state.isEmpty) {
|
||||
return SizedBox.expand(
|
||||
child: Center(
|
||||
child: _buildEmptyState(
|
||||
connectedState.isConnected,
|
||||
state,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return _buildImageGrid(state);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
create: (_) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(context.watch<LocalUserAccount>().id)!,
|
||||
),
|
||||
builder: (_, __) => const DocumentSearchPage(),
|
||||
return Provider(
|
||||
create: (_) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.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(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,183 +92,246 @@ class _DocumentUploadPreparationPageState
|
||||
builder: (context, state) {
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
// Title
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
name: DocumentModel.titleKey,
|
||||
initialValue:
|
||||
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
return S.of(context)!.thisFieldIsRequired;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context)!.title,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[DocumentModel.titleKey]
|
||||
?.didChange("");
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange("");
|
||||
}
|
||||
},
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle:
|
||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
leading: BackButton(
|
||||
color: _titleColor,
|
||||
),
|
||||
errorText: _errors[DocumentModel.titleKey],
|
||||
),
|
||||
onChanged: (value) {
|
||||
final String transformedValue =
|
||||
_formatFilename(value ?? '');
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Filename
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
readOnly: _syncTitleAndFilename,
|
||||
enabled: !_syncTitleAndFilename,
|
||||
name: fkFileName,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context)!.fileName,
|
||||
suffixText: widget.fileExtension,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => _formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(''),
|
||||
pinned: true,
|
||||
expandedHeight: 150,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Image.memory(
|
||||
widget.fileBytes,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
title: Text(
|
||||
S.of(context)!.prepareDocument,
|
||||
style: TextStyle(
|
||||
color: _titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
initialValue: widget.filename ??
|
||||
"scan_${fileNameDateFormat.format(_now)}",
|
||||
),
|
||||
// Synchronize title and filename
|
||||
SwitchListTile(
|
||||
value: _syncTitleAndFilename,
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() => _syncTitleAndFilename = value,
|
||||
);
|
||||
if (_syncTitleAndFilename) {
|
||||
final String transformedValue = _formatFilename(_formKey
|
||||
.currentState
|
||||
?.fields[DocumentModel.titleKey]
|
||||
?.value as String);
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
S.of(context)!.synchronizeTitleAndFilename,
|
||||
),
|
||||
),
|
||||
// Created at
|
||||
FormBuilderDateTimePicker(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
format: DateFormat.yMMMMd(),
|
||||
inputType: InputType.date,
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: null,
|
||||
onChanged: (value) {
|
||||
setState(() => _showDatePickerDeleteIcon = value != null);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.calendar_month_outlined),
|
||||
labelText: S.of(context)!.createdAt + " *",
|
||||
suffixIcon: _showDatePickerDeleteIcon
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState!
|
||||
.fields[DocumentModel.createdKey]
|
||||
?.didChange(null);
|
||||
},
|
||||
bottom: _isUploadLoading
|
||||
? const PreferredSize(
|
||||
child: LinearProgressIndicator(),
|
||||
preferredSize: Size.fromHeight(4.0),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
// Correspondent
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
],
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle:
|
||||
NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
child: AddCorrespondentPage(initialName: initialName),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: state.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
),
|
||||
// Document type
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
SliverList.list(
|
||||
children: [
|
||||
// Title
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
name: DocumentModel.titleKey,
|
||||
initialValue: widget.title ??
|
||||
"scan_${fileNameDateFormat.format(_now)}",
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
return S.of(context)!.thisFieldIsRequired;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context)!.title,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState
|
||||
?.fields[DocumentModel.titleKey]
|
||||
?.didChange("");
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange("");
|
||||
}
|
||||
},
|
||||
),
|
||||
errorText: _errors[DocumentModel.titleKey],
|
||||
),
|
||||
onChanged: (value) {
|
||||
final String transformedValue =
|
||||
_formatFilename(value ?? '');
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Filename
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
readOnly: _syncTitleAndFilename,
|
||||
enabled: !_syncTitleAndFilename,
|
||||
name: fkFileName,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context)!.fileName,
|
||||
suffixText: widget.fileExtension,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => _formKey
|
||||
.currentState?.fields[fkFileName]
|
||||
?.didChange(''),
|
||||
),
|
||||
),
|
||||
initialValue: widget.filename ??
|
||||
"scan_${fileNameDateFormat.format(_now)}",
|
||||
),
|
||||
// Synchronize title and filename
|
||||
SwitchListTile(
|
||||
value: _syncTitleAndFilename,
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() => _syncTitleAndFilename = value,
|
||||
);
|
||||
if (_syncTitleAndFilename) {
|
||||
final String transformedValue =
|
||||
_formatFilename(_formKey
|
||||
.currentState
|
||||
?.fields[DocumentModel.titleKey]
|
||||
?.value as String);
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
S.of(context)!.synchronizeTitleAndFilename,
|
||||
),
|
||||
),
|
||||
// Created at
|
||||
FormBuilderDateTimePicker(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
format: DateFormat.yMMMMd(),
|
||||
inputType: InputType.date,
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: null,
|
||||
onChanged: (value) {
|
||||
setState(() =>
|
||||
_showDatePickerDeleteIcon = value != null);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon:
|
||||
const Icon(Icons.calendar_month_outlined),
|
||||
labelText: S.of(context)!.createdAt + " *",
|
||||
suffixIcon: _showDatePickerDeleteIcon
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState!
|
||||
.fields[DocumentModel.createdKey]
|
||||
?.didChange(null);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
// Correspondent
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) =>
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
child: AddCorrespondentPage(
|
||||
initialName: initialName),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: state.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
),
|
||||
// Document type
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) =>
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
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),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
),
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewTags)
|
||||
TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: state.tags,
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context)!.uploadInferValuesHint,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.justify,
|
||||
).padded(),
|
||||
const SizedBox(height: 300),
|
||||
].padded(),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
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),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
),
|
||||
if (context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||
TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: state.tags,
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context)!.uploadInferValuesHint,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
);
|
||||
},
|
||||
),
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,15 +23,23 @@ 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(
|
||||
enabled: enableHero,
|
||||
child: Hero(
|
||||
tag: "thumb_${document.id}",
|
||||
child: _buildPreview(context),
|
||||
return GestureDetector(
|
||||
onTap: isClickable
|
||||
? () {
|
||||
DocumentPreviewRoute($extra: document).push(context);
|
||||
}
|
||||
: null,
|
||||
child: HeroMode(
|
||||
enabled: enableHero,
|
||||
child: Hero(
|
||||
tag: "thumb_${document.id}",
|
||||
child: _buildPreview(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,16 +171,22 @@ class HomeShellWidget extends StatelessWidget {
|
||||
create: (context) =>
|
||||
DocumentScannerCubit(context.read()),
|
||||
),
|
||||
if (currentLocalUser.paperlessUser.canViewDocuments &&
|
||||
currentLocalUser.paperlessUser.canViewTags)
|
||||
Provider(
|
||||
create: (context) => InboxCubit(
|
||||
Provider(
|
||||
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(
|
||||
context.read(),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ class LabelText<T extends Label> extends StatelessWidget {
|
||||
const LabelText({
|
||||
super.key,
|
||||
this.style,
|
||||
this.placeholder = "",
|
||||
this.placeholder = "-",
|
||||
required this.label,
|
||||
});
|
||||
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -47,7 +47,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
await widget.onDelete(cubit.savedView);
|
||||
Navigator.pop(context);
|
||||
context.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
178
lib/main.dart
178
lib/main.dart
@@ -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),
|
||||
);
|
||||
},
|
||||
child: MaterialApp.router(
|
||||
return BlocListener<AuthenticationCubit, AuthenticationState>(
|
||||
listener: (context, state) {
|
||||
state.when(
|
||||
unauthenticated: () => _router.goNamed(R.login),
|
||||
requriresLocalAuthentication: () => _router.goNamed(R.verifyIdentity),
|
||||
switchingAccounts: () => _router.goNamed(R.switchingAccounts),
|
||||
authenticated: (localUserId) => _router.goNamed(R.landing),
|
||||
);
|
||||
},
|
||||
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(),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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."),
|
||||
};
|
||||
},
|
||||
),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user