mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 21:15:50 -06:00
feat: Update translations, finish saved views rework, some other fixes
This commit is contained in:
@@ -10,7 +10,9 @@ import 'package:hive_flutter/adapters.dart';
|
|||||||
/// [callback] to return and returns the calculated value. Closes the box after.
|
/// [callback] to return and returns the calculated value. Closes the box after.
|
||||||
///
|
///
|
||||||
Future<R?> withEncryptedBox<T, R>(
|
Future<R?> withEncryptedBox<T, R>(
|
||||||
String name, FutureOr<R?> Function(Box<T> box) callback) async {
|
String name,
|
||||||
|
FutureOr<R?> Function(Box<T> box) callback,
|
||||||
|
) async {
|
||||||
final key = await _getEncryptedBoxKey();
|
final key = await _getEncryptedBoxKey();
|
||||||
final box = await Hive.openBox<T>(
|
final box = await Hive.openBox<T>(
|
||||||
name,
|
name,
|
||||||
@@ -22,7 +24,11 @@ Future<R?> withEncryptedBox<T, R>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> _getEncryptedBoxKey() async {
|
Future<Uint8List> _getEncryptedBoxKey() async {
|
||||||
const secureStorage = FlutterSecureStorage();
|
const secureStorage = FlutterSecureStorage(
|
||||||
|
aOptions: AndroidOptions(
|
||||||
|
encryptedSharedPreferences: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (!await secureStorage.containsKey(key: 'key')) {
|
if (!await secureStorage.containsKey(key: 'key')) {
|
||||||
final key = Hive.generateSecureKey();
|
final key = Hive.generateSecureKey();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.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_settings.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.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_settings.dart';
|
||||||
|
|
||||||
part 'local_user_account.g.dart';
|
part 'local_user_account.g.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import 'package:paperless_mobile/features/document_bulk_action/view/widgets/full
|
|||||||
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
|
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.dart';
|
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|||||||
@@ -8,25 +8,26 @@ abstract class PersistentRepository<T> extends HydratedCubit<T> {
|
|||||||
PersistentRepository(T initialState) : super(initialState);
|
PersistentRepository(T initialState) : super(initialState);
|
||||||
|
|
||||||
void addListener(
|
void addListener(
|
||||||
Object source, {
|
Object subscriber, {
|
||||||
required void Function(T) onChanged,
|
required void Function(T) onChanged,
|
||||||
}) {
|
}) {
|
||||||
onChanged(state);
|
onChanged(state);
|
||||||
_subscribers.putIfAbsent(source, () {
|
_subscribers.putIfAbsent(subscriber, () {
|
||||||
return stream.listen((event) => onChanged(event));
|
return stream.listen((event) => onChanged(event));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeListener(Object source) async {
|
void removeListener(Object source) async {
|
||||||
await _subscribers[source]?.cancel();
|
_subscribers
|
||||||
_subscribers.remove(source);
|
..[source]?.cancel()
|
||||||
|
..remove(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_subscribers.forEach((key, subscription) {
|
for (final subscriber in _subscribers.values) {
|
||||||
subscription.cancel();
|
subscriber.cancel();
|
||||||
});
|
}
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,25 +54,26 @@ String translateError(BuildContext context, ErrorCode code) {
|
|||||||
ErrorCode.suggestionsQueryError => S.of(context)!.couldNotLoadSuggestions,
|
ErrorCode.suggestionsQueryError => S.of(context)!.couldNotLoadSuggestions,
|
||||||
ErrorCode.acknowledgeTasksError => S.of(context)!.couldNotAcknowledgeTasks,
|
ErrorCode.acknowledgeTasksError => S.of(context)!.couldNotAcknowledgeTasks,
|
||||||
ErrorCode.correspondentDeleteFailed =>
|
ErrorCode.correspondentDeleteFailed =>
|
||||||
"Could not delete correspondent, please try again.", //TODO: INTL
|
S.of(context)!.couldNotDeleteCorrespondent,
|
||||||
ErrorCode.documentTypeDeleteFailed =>
|
ErrorCode.documentTypeDeleteFailed =>
|
||||||
"Could not delete document type, please try again.",
|
S.of(context)!.couldNotDeleteDocumentType,
|
||||||
ErrorCode.tagDeleteFailed => "Could not delete tag, please try again.",
|
ErrorCode.tagDeleteFailed => S.of(context)!.couldNotDeleteTag,
|
||||||
ErrorCode.correspondentUpdateFailed =>
|
|
||||||
"Could not update correspondent, please try again.",
|
|
||||||
ErrorCode.documentTypeUpdateFailed =>
|
|
||||||
"Could not update document type, please try again.",
|
|
||||||
ErrorCode.tagUpdateFailed => "Could not update tag, please try again.",
|
|
||||||
ErrorCode.storagePathDeleteFailed =>
|
ErrorCode.storagePathDeleteFailed =>
|
||||||
"Could not delete storage path, please try again.",
|
S.of(context)!.couldNotDeleteStoragePath,
|
||||||
|
ErrorCode.correspondentUpdateFailed =>
|
||||||
|
S.of(context)!.couldNotUpdateCorrespondent,
|
||||||
|
ErrorCode.documentTypeUpdateFailed =>
|
||||||
|
S.of(context)!.couldNotUpdateDocumentType,
|
||||||
|
ErrorCode.tagUpdateFailed => S.of(context)!.couldNotUpdateTag,
|
||||||
ErrorCode.storagePathUpdateFailed =>
|
ErrorCode.storagePathUpdateFailed =>
|
||||||
"Could not update storage path, please try again.",
|
S.of(context)!.couldNotUpdateStoragePath,
|
||||||
ErrorCode.serverInformationLoadFailed =>
|
ErrorCode.serverInformationLoadFailed =>
|
||||||
"Could not load server information.",
|
S.of(context)!.couldNotLoadServerInformation,
|
||||||
ErrorCode.serverStatisticsLoadFailed => "Could not load server statistics.",
|
ErrorCode.serverStatisticsLoadFailed =>
|
||||||
ErrorCode.uiSettingsLoadFailed => "Could not load UI settings",
|
S.of(context)!.couldNotLoadStatistics,
|
||||||
ErrorCode.loadTasksError => "Could not load tasks.",
|
ErrorCode.uiSettingsLoadFailed => S.of(context)!.couldNotLoadUISettings,
|
||||||
ErrorCode.userNotFound => "User could not be found.",
|
ErrorCode.loadTasksError => S.of(context)!.couldNotLoadTasks,
|
||||||
ErrorCode.updateSavedViewError => "Could not update saved view.",
|
ErrorCode.userNotFound => S.of(context)!.userNotFound,
|
||||||
|
ErrorCode.updateSavedViewError => S.of(context)!.couldNotUpdateSavedView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
31
lib/core/widgets/dialog_utils/pop_with_unsaved_changes.dart
Normal file
31
lib/core/widgets/dialog_utils/pop_with_unsaved_changes.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/dialog_utils/unsaved_changes_warning_dialog.dart';
|
||||||
|
|
||||||
|
class PopWithUnsavedChanges extends StatelessWidget {
|
||||||
|
final bool Function() hasChangesPredicate;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const PopWithUnsavedChanges({
|
||||||
|
super.key,
|
||||||
|
required this.hasChangesPredicate,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (hasChangesPredicate()) {
|
||||||
|
final shouldPop = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const UnsavedChangesWarningDialog(),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
return shouldPop;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class UnsavedChangesWarningDialog extends StatelessWidget {
|
||||||
|
const UnsavedChangesWarningDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text("Discard changes?"),
|
||||||
|
content: Text(
|
||||||
|
"You have unsaved changes. Do you want to continue without saving? Your changes will be discarded.",
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
DialogCancelButton(),
|
||||||
|
DialogConfirmButton(
|
||||||
|
label: S.of(context)!.continueLabel,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
|
|
||||||
class EmptyState extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final Widget? bottomChild;
|
|
||||||
|
|
||||||
const EmptyState({
|
|
||||||
Key? key,
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
this.bottomChild,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final size = MediaQuery.of(context).size;
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: size.height / 3,
|
|
||||||
width: size.width / 3,
|
|
||||||
child: SvgPicture.asset("assets/images/empty-state.svg"),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (bottomChild != null) ...[bottomChild!] else ...[]
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,10 +15,8 @@ import 'package:paperless_mobile/features/document_details/view/widgets/document
|
|||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
|
||||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.dart';
|
||||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||||
@@ -19,7 +19,6 @@ import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_
|
|||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
|
||||||
class DocumentEditPage extends StatefulWidget {
|
class DocumentEditPage extends StatefulWidget {
|
||||||
@@ -46,253 +45,257 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||||
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
return PopWithUnsavedChanges(
|
||||||
builder: (context, state) {
|
hasChangesPredicate: () => _formKey.currentState?.isDirty ?? false,
|
||||||
final filteredSuggestions = state.suggestions?.documentDifference(
|
child: BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||||
context.read<DocumentEditCubit>().state.document);
|
builder: (context, state) {
|
||||||
return DefaultTabController(
|
final filteredSuggestions = state.suggestions?.documentDifference(
|
||||||
length: 2,
|
context.read<DocumentEditCubit>().state.document);
|
||||||
child: Scaffold(
|
return DefaultTabController(
|
||||||
resizeToAvoidBottomInset: false,
|
length: 2,
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
child: Scaffold(
|
||||||
heroTag: "fab_document_edit",
|
resizeToAvoidBottomInset: false,
|
||||||
onPressed: () => _onSubmit(state.document),
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.save),
|
heroTag: "fab_document_edit",
|
||||||
label: Text(S.of(context)!.saveChanges),
|
onPressed: () => _onSubmit(state.document),
|
||||||
),
|
icon: const Icon(Icons.save),
|
||||||
appBar: AppBar(
|
label: Text(S.of(context)!.saveChanges),
|
||||||
title: Text(S.of(context)!.editDocument),
|
|
||||||
bottom: TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(
|
|
||||||
text: S.of(context)!.overview,
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
text: S.of(context)!.content,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
appBar: AppBar(
|
||||||
extendBody: true,
|
title: Text(S.of(context)!.editDocument),
|
||||||
body: Padding(
|
bottom: TabBar(
|
||||||
padding: EdgeInsets.only(
|
tabs: [
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
Tab(text: S.of(context)!.overview),
|
||||||
top: 8,
|
Tab(text: S.of(context)!.content)
|
||||||
left: 8,
|
|
||||||
right: 8,
|
|
||||||
),
|
|
||||||
child: FormBuilder(
|
|
||||||
key: _formKey,
|
|
||||||
child: TabBarView(
|
|
||||||
children: [
|
|
||||||
ListView(
|
|
||||||
children: [
|
|
||||||
_buildTitleFormField(state.document.title).padded(),
|
|
||||||
_buildCreatedAtFormField(
|
|
||||||
state.document.created,
|
|
||||||
filteredSuggestions,
|
|
||||||
).padded(),
|
|
||||||
// Correspondent form field
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
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
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(),
|
|
||||||
// Tag form field
|
|
||||||
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())
|
|
||||||
.isNotEmpty ??
|
|
||||||
false)
|
|
||||||
_buildSuggestionsSkeleton<int>(
|
|
||||||
suggestions:
|
|
||||||
(filteredSuggestions?.tags.toSet() ?? {}),
|
|
||||||
itemBuilder: (context, itemData) {
|
|
||||||
final tag = state.tags[itemData]!;
|
|
||||||
return ActionChip(
|
|
||||||
label: Text(
|
|
||||||
tag.name,
|
|
||||||
style: TextStyle(color: tag.textColor),
|
|
||||||
),
|
|
||||||
backgroundColor: tag.color,
|
|
||||||
onPressed: () {
|
|
||||||
final currentTags = _formKey.currentState
|
|
||||||
?.fields[fkTags]?.value as TagsQuery;
|
|
||||||
_formKey.currentState?.fields[fkTags]
|
|
||||||
?.didChange(
|
|
||||||
currentTags.maybeWhen(
|
|
||||||
ids: (include, exclude) =>
|
|
||||||
TagsQuery.ids(
|
|
||||||
include: [...include, itemData],
|
|
||||||
exclude: exclude),
|
|
||||||
orElse: () =>
|
|
||||||
TagsQuery.ids(include: [itemData]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Prevent tags from being hidden by fab
|
|
||||||
const SizedBox(height: 64),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
FormBuilderTextField(
|
|
||||||
name: fkContent,
|
|
||||||
maxLines: null,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
initialValue: state.document.content,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 84),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
extendBody: true,
|
||||||
);
|
body: Padding(
|
||||||
},
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
top: 8,
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
child: FormBuilder(
|
||||||
|
key: _formKey,
|
||||||
|
child: TabBarView(
|
||||||
|
children: [
|
||||||
|
ListView(
|
||||||
|
children: [
|
||||||
|
_buildTitleFormField(state.document.title).padded(),
|
||||||
|
_buildCreatedAtFormField(
|
||||||
|
state.document.created,
|
||||||
|
filteredSuggestions,
|
||||||
|
).padded(),
|
||||||
|
// Correspondent form field
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padded(),
|
||||||
|
// Tag form field
|
||||||
|
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())
|
||||||
|
.isNotEmpty ??
|
||||||
|
false)
|
||||||
|
_buildSuggestionsSkeleton<int>(
|
||||||
|
suggestions:
|
||||||
|
(filteredSuggestions?.tags.toSet() ?? {}),
|
||||||
|
itemBuilder: (context, itemData) {
|
||||||
|
final tag = state.tags[itemData]!;
|
||||||
|
return ActionChip(
|
||||||
|
label: Text(
|
||||||
|
tag.name,
|
||||||
|
style: TextStyle(color: tag.textColor),
|
||||||
|
),
|
||||||
|
backgroundColor: tag.color,
|
||||||
|
onPressed: () {
|
||||||
|
final currentTags = _formKey.currentState
|
||||||
|
?.fields[fkTags]?.value as TagsQuery;
|
||||||
|
_formKey.currentState?.fields[fkTags]
|
||||||
|
?.didChange(
|
||||||
|
currentTags.maybeWhen(
|
||||||
|
ids: (include, exclude) =>
|
||||||
|
TagsQuery.ids(include: [
|
||||||
|
...include,
|
||||||
|
itemData
|
||||||
|
], exclude: exclude),
|
||||||
|
orElse: () => TagsQuery.ids(
|
||||||
|
include: [itemData]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Prevent tags from being hidden by fab
|
||||||
|
const SizedBox(height: 64),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: fkContent,
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
initialValue: state.document.content,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 84),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
|
||||||
import 'package:paperless_mobile/core/service/file_description.dart';
|
import 'package:paperless_mobile/core/service/file_description.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
@@ -58,6 +57,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
top: true,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState>
|
|||||||
this.api,
|
this.api,
|
||||||
this.notifier,
|
this.notifier,
|
||||||
this._userAppState,
|
this._userAppState,
|
||||||
) : super(DocumentSearchState(
|
) : super(
|
||||||
searchHistory: _userAppState.documentSearchHistory)) {
|
DocumentSearchState(
|
||||||
|
searchHistory: _userAppState.documentSearchHistory),
|
||||||
|
) {
|
||||||
notifier.addListener(
|
notifier.addListener(
|
||||||
this,
|
this,
|
||||||
onDeleted: remove,
|
onDeleted: remove,
|
||||||
@@ -34,22 +36,25 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> search(String query) async {
|
Future<void> search(String query) async {
|
||||||
emit(state.copyWith(
|
final normalizedQuery = query.trim();
|
||||||
isLoading: true,
|
emit(
|
||||||
suggestions: [],
|
state.copyWith(
|
||||||
view: SearchView.results,
|
isLoading: true,
|
||||||
));
|
suggestions: [],
|
||||||
|
view: SearchView.results,
|
||||||
|
),
|
||||||
|
);
|
||||||
final searchFilter = DocumentFilter(
|
final searchFilter = DocumentFilter(
|
||||||
query: TextQuery.extended(query),
|
query: TextQuery.extended(normalizedQuery),
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateFilter(filter: searchFilter);
|
await updateFilter(filter: searchFilter);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
searchHistory: [
|
searchHistory: [
|
||||||
query,
|
normalizedQuery,
|
||||||
...state.searchHistory
|
...state.searchHistory
|
||||||
.whereNot((previousQuery) => previousQuery == query)
|
.whereNot((previousQuery) => previousQuery == normalizedQuery)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,75 +21,70 @@ class DocumentSearchBar extends StatefulWidget {
|
|||||||
class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return OpenContainer(
|
||||||
margin: EdgeInsets.only(top: 8),
|
transitionDuration: const Duration(milliseconds: 200),
|
||||||
child: OpenContainer(
|
transitionType: ContainerTransitionType.fadeThrough,
|
||||||
transitionDuration: const Duration(milliseconds: 200),
|
closedElevation: 1,
|
||||||
transitionType: ContainerTransitionType.fadeThrough,
|
middleColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
closedElevation: 1,
|
openColor: Theme.of(context).colorScheme.background,
|
||||||
middleColor: Theme.of(context).colorScheme.surfaceVariant,
|
closedColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
openColor: Theme.of(context).colorScheme.background,
|
closedShape: RoundedRectangleBorder(
|
||||||
closedColor: Theme.of(context).colorScheme.surfaceVariant,
|
borderRadius: BorderRadius.circular(56),
|
||||||
closedShape: RoundedRectangleBorder(
|
),
|
||||||
borderRadius: BorderRadius.circular(56),
|
closedBuilder: (_, action) {
|
||||||
),
|
return InkWell(
|
||||||
closedBuilder: (_, action) {
|
onTap: action,
|
||||||
return InkWell(
|
child: ConstrainedBox(
|
||||||
onTap: action,
|
constraints: const BoxConstraints(
|
||||||
child: ConstrainedBox(
|
maxWidth: 720,
|
||||||
constraints: const BoxConstraints(
|
minWidth: 360,
|
||||||
maxWidth: 720,
|
maxHeight: 56,
|
||||||
minWidth: 360,
|
minHeight: 48,
|
||||||
maxHeight: 56,
|
),
|
||||||
minHeight: 48,
|
child: Row(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Row(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
Flexible(
|
||||||
children: [
|
child: Padding(
|
||||||
Flexible(
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
child: Row(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
IconButton(
|
||||||
children: [
|
icon: const Icon(Icons.menu),
|
||||||
IconButton(
|
onPressed: Scaffold.of(context).openDrawer,
|
||||||
icon: const Icon(Icons.menu),
|
),
|
||||||
onPressed: Scaffold.of(context).openDrawer,
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
S.of(context)!.searchDocuments,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
),
|
||||||
child: Text(
|
],
|
||||||
S.of(context)!.searchDocuments,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge
|
|
||||||
?.copyWith(
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildUserAvatar(context),
|
),
|
||||||
],
|
_buildUserAvatar(context),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
openBuilder: (_, action) {
|
},
|
||||||
return Provider(
|
openBuilder: (_, action) {
|
||||||
create: (_) => DocumentSearchCubit(
|
return Provider(
|
||||||
context.read(),
|
create: (_) => DocumentSearchCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
context.read(),
|
||||||
.get(context.read<LocalUserAccount>().id)!,
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
),
|
.get(context.read<LocalUserAccount>().id)!,
|
||||||
child: const DocumentSearchPage(),
|
),
|
||||||
);
|
child: const DocumentSearchPage(),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import 'dart:math' as math;
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/view/remove_history_entry_dialog.dart';
|
import 'package:paperless_mobile/features/document_search/view/remove_history_entry_dialog.dart';
|
||||||
@@ -188,7 +186,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
S.of(context)!.results,
|
S.of(context)!.results,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.labelMedium,
|
||||||
),
|
),
|
||||||
BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
|
BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -200,15 +198,15 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
).padded();
|
).paddedLTRB(16, 8, 8, 8);
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(child: header),
|
SliverToBoxAdapter(child: header),
|
||||||
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty)
|
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(S.of(context)!.noMatchesFound),
|
child: Text(S.of(context)!.noDocumentsFound),
|
||||||
),
|
).paddedOnly(top: 8),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
SliverAdaptiveDocumentsView(
|
SliverAdaptiveDocumentsView(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dar
|
|||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
|
|
||||||
class SliverSearchBar extends StatelessWidget {
|
class SliverSearchBar extends StatelessWidget {
|
||||||
final bool floating;
|
final bool floating;
|
||||||
@@ -22,14 +24,13 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
if (context.watch<LocalUserAccount>().paperlessUser.canViewDocuments) {
|
if (context.watch<LocalUserAccount>().paperlessUser.canViewDocuments) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
toolbarHeight: kToolbarHeight,
|
titleSpacing: 8,
|
||||||
flexibleSpace: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: const DocumentSearchBar(),
|
|
||||||
),
|
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
|
title: DocumentSearchBar(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:badges/badges.dart' as b;
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:defer_pointer/defer_pointer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
@@ -18,8 +18,8 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/confi
|
|||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
|
||||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
@@ -43,45 +43,58 @@ class DocumentsPage extends StatefulWidget {
|
|||||||
State<DocumentsPage> createState() => _DocumentsPageState();
|
State<DocumentsPage> createState() => _DocumentsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentsPageState extends State<DocumentsPage>
|
class _DocumentsPageState extends State<DocumentsPage> {
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
final SliverOverlapAbsorberHandle searchBarHandle =
|
final SliverOverlapAbsorberHandle searchBarHandle =
|
||||||
SliverOverlapAbsorberHandle();
|
SliverOverlapAbsorberHandle();
|
||||||
|
|
||||||
final SliverOverlapAbsorberHandle savedViewsHandle =
|
final SliverOverlapAbsorberHandle savedViewsHandle =
|
||||||
SliverOverlapAbsorberHandle();
|
SliverOverlapAbsorberHandle();
|
||||||
late final TabController _tabController;
|
|
||||||
|
|
||||||
int _currentTab = 0;
|
final _nestedScrollViewKey = GlobalKey<NestedScrollViewState>();
|
||||||
|
|
||||||
final _savedViewsExpansionController = ExpansionTileController();
|
final _savedViewsExpansionController = ExpansionTileController();
|
||||||
|
bool _showExtendedFab = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final showSavedViews =
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<LocalUserAccount>().paperlessUser.canViewSavedViews;
|
_nestedScrollViewKey.currentState!.innerController
|
||||||
_tabController = TabController(
|
.addListener(_scrollExtentChangedListener);
|
||||||
length: showSavedViews ? 2 : 1,
|
});
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
// Future.wait([
|
|
||||||
// context.read<DocumentsCubit>().reload(),
|
|
||||||
// context.read<SavedViewCubit>().reload(),
|
|
||||||
// ]).onError<PaperlessApiException>(
|
|
||||||
// (error, stackTrace) {
|
|
||||||
// showErrorMessage(context, error, stackTrace);
|
|
||||||
// return [];
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
_tabController.addListener(_tabChangesListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tabChangesListener() {
|
Future<void> _reloadData() async {
|
||||||
setState(() => _currentTab = _tabController.index);
|
try {
|
||||||
|
await Future.wait([
|
||||||
|
context.read<DocumentsCubit>().reload(),
|
||||||
|
context.read<SavedViewCubit>().reload(),
|
||||||
|
context.read<LabelCubit>().reload(),
|
||||||
|
]);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
showGenericError(context, error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scrollExtentChangedListener() {
|
||||||
|
const threshold = 400;
|
||||||
|
final offset =
|
||||||
|
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
||||||
|
if (offset < threshold && _showExtendedFab == false) {
|
||||||
|
setState(() {
|
||||||
|
_showExtendedFab = true;
|
||||||
|
});
|
||||||
|
} else if (offset >= threshold && _showExtendedFab == true) {
|
||||||
|
setState(() {
|
||||||
|
_showExtendedFab = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_nestedScrollViewKey.currentState?.innerController
|
||||||
|
.removeListener(_scrollExtentChangedListener);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +122,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
previous != ConnectivityState.connected &&
|
previous != ConnectivityState.connected &&
|
||||||
current == ConnectivityState.connected,
|
current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
try {
|
_reloadData();
|
||||||
context.read<DocumentsCubit>().reload();
|
|
||||||
} on PaperlessApiException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
builder: (context, connectivityState) {
|
builder: (context, connectivityState) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
@@ -122,59 +131,104 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
|
||||||
final show = state.selection.isEmpty;
|
final show = state.selection.isEmpty;
|
||||||
final canReset = state.filter.appliedFiltersCount > 0;
|
final canReset = state.filter.appliedFiltersCount > 0;
|
||||||
return AnimatedScale(
|
if (show) {
|
||||||
scale: show ? 1 : 0,
|
return Column(
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
if (canReset)
|
DeferredPointerHandler(
|
||||||
Padding(
|
child: Stack(
|
||||||
padding: const EdgeInsets.all(8.0),
|
clipBehavior: Clip.none,
|
||||||
child: FloatingActionButton.small(
|
children: [
|
||||||
heroTag: "fab_documents_page_reset_filter",
|
FloatingActionButton.extended(
|
||||||
backgroundColor: Theme.of(context)
|
extendedPadding: _showExtendedFab
|
||||||
.colorScheme
|
? null
|
||||||
.onPrimaryContainer,
|
: const EdgeInsets.symmetric(
|
||||||
onPressed: () {
|
horizontal: 16),
|
||||||
_onResetFilter();
|
heroTag: "fab_documents_page_filter",
|
||||||
},
|
label: AnimatedSwitcher(
|
||||||
child: Icon(
|
duration: const Duration(milliseconds: 150),
|
||||||
Icons.refresh,
|
transitionBuilder: (child, animation) {
|
||||||
color: Theme.of(context)
|
return FadeTransition(
|
||||||
.colorScheme
|
opacity: animation,
|
||||||
.primaryContainer,
|
child: SizeTransition(
|
||||||
|
sizeFactor: animation,
|
||||||
|
axis: Axis.horizontal,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _showExtendedFab
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.filter_alt_outlined,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
S.of(context)!.filterDocuments,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Icon(Icons.filter_alt_outlined),
|
||||||
|
),
|
||||||
|
onPressed: _openDocumentFilter,
|
||||||
),
|
),
|
||||||
),
|
if (canReset)
|
||||||
|
Positioned(
|
||||||
|
top: -20,
|
||||||
|
right: -8,
|
||||||
|
child: DeferPointer(
|
||||||
|
paintOnTop: true,
|
||||||
|
child: Material(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () {
|
||||||
|
HapticFeedback.mediumImpact();
|
||||||
|
_onResetFilter();
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if (_showExtendedFab)
|
||||||
|
Text(
|
||||||
|
"Reset (${state.filter.appliedFiltersCount})",
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelLarge
|
||||||
|
?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onError,
|
||||||
|
),
|
||||||
|
).padded()
|
||||||
|
else
|
||||||
|
Icon(
|
||||||
|
Icons.replay,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onError,
|
||||||
|
).padded(4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
b.Badge(
|
|
||||||
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
|
||||||
showBadge: appliedFiltersCount > 0,
|
|
||||||
badgeContent: Text(
|
|
||||||
'$appliedFiltersCount',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
animationType: b.BadgeAnimationType.fade,
|
|
||||||
badgeColor: Colors.red,
|
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
child: Builder(builder: (context) {
|
|
||||||
return FloatingActionButton(
|
|
||||||
heroTag: "fab_documents_page_filter",
|
|
||||||
child: const Icon(Icons.filter_alt_outlined),
|
|
||||||
onPressed: _openDocumentFilter,
|
|
||||||
);
|
|
||||||
})),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
@@ -190,94 +244,41 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: NestedScrollView(
|
||||||
children: [
|
key: _nestedScrollViewKey,
|
||||||
NestedScrollView(
|
floatHeaderSlivers: true,
|
||||||
floatHeaderSlivers: true,
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
SliverOverlapAbsorber(
|
||||||
SliverOverlapAbsorber(
|
handle: searchBarHandle,
|
||||||
handle: searchBarHandle,
|
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
builder: (context, state) {
|
||||||
builder: (context, state) {
|
if (state.selection.isEmpty) {
|
||||||
if (state.selection.isEmpty) {
|
return SliverSearchBar(
|
||||||
return SliverSearchBar(
|
floating: true,
|
||||||
floating: true,
|
titleText: S.of(context)!.documents,
|
||||||
titleText: S.of(context)!.documents,
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
return DocumentSelectionSliverAppBar(
|
||||||
return DocumentSelectionSliverAppBar(
|
state: state,
|
||||||
state: state,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverOverlapAbsorber(
|
|
||||||
handle: savedViewsHandle,
|
|
||||||
sliver: SliverPinnedHeader(
|
|
||||||
child: Material(
|
|
||||||
child: _buildViewActions(),
|
|
||||||
elevation: 4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// SliverOverlapAbsorber(
|
|
||||||
// handle: tabBarHandle,
|
|
||||||
// sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
||||||
// builder: (context, state) {
|
|
||||||
// if (state.selection.isNotEmpty) {
|
|
||||||
// return const SliverToBoxAdapter(
|
|
||||||
// child: SizedBox.shrink(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// return SliverPersistentHeader(
|
|
||||||
// pinned: true,
|
|
||||||
// delegate:
|
|
||||||
// CustomizableSliverPersistentHeaderDelegate(
|
|
||||||
// minExtent: kTextTabBarHeight,
|
|
||||||
// maxExtent: kTextTabBarHeight,
|
|
||||||
// child: ColoredTabBar(
|
|
||||||
// tabBar: TabBar(
|
|
||||||
// controller: _tabController,
|
|
||||||
// tabs: [
|
|
||||||
// Tab(text: S.of(context)!.documents),
|
|
||||||
// if (context
|
|
||||||
// .watch<LocalUserAccount>()
|
|
||||||
// .paperlessUser
|
|
||||||
// .canViewSavedViews)
|
|
||||||
// Tab(text: S.of(context)!.views),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
body: NotificationListener<ScrollNotification>(
|
|
||||||
onNotification: (notification) {
|
|
||||||
final metrics = notification.metrics;
|
|
||||||
if (metrics.maxScrollExtent == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
final desiredTab =
|
|
||||||
(metrics.pixels / metrics.maxScrollExtent)
|
|
||||||
.round();
|
|
||||||
if (metrics.axis == Axis.horizontal &&
|
|
||||||
_currentTab != desiredTab) {
|
|
||||||
setState(() => _currentTab = desiredTab);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
child: _buildDocumentsTab(
|
),
|
||||||
connectivityState,
|
),
|
||||||
context,
|
SliverOverlapAbsorber(
|
||||||
|
handle: savedViewsHandle,
|
||||||
|
sliver: SliverPinnedHeader(
|
||||||
|
child: Material(
|
||||||
|
child: _buildViewActions(),
|
||||||
|
elevation: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildSavedViewChangedIndicator(),
|
|
||||||
],
|
],
|
||||||
|
body: _buildDocumentsTab(
|
||||||
|
connectivityState,
|
||||||
|
context,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -287,82 +288,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSavedViewChangedIndicator() {
|
|
||||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final savedViewCubit = context.watch<SavedViewCubit>();
|
|
||||||
final activeView = savedViewCubit.state.maybeMap(
|
|
||||||
loaded: (savedViewState) {
|
|
||||||
if (state.filter.selectedView != null) {
|
|
||||||
return savedViewState.savedViews[state.filter.selectedView!];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
final viewHasChanged =
|
|
||||||
activeView != null && activeView.toDocumentFilter() != state.filter;
|
|
||||||
return AnimatedScale(
|
|
||||||
scale: viewHasChanged ? 1 : 0,
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 24),
|
|
||||||
child: Material(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.surfaceVariant
|
|
||||||
.withOpacity(0.9),
|
|
||||||
child: InkWell(
|
|
||||||
customBorder: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
await _updateCurrentSavedView();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
|
|
||||||
child: Text(
|
|
||||||
"Update selected view",
|
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widget _buildSavedViewsTab(
|
|
||||||
// ConnectivityState connectivityState,
|
|
||||||
// BuildContext context,
|
|
||||||
// ) {
|
|
||||||
// return RefreshIndicator(
|
|
||||||
// edgeOffset: kTextTabBarHeight,
|
|
||||||
// onRefresh: _onReloadSavedViews,
|
|
||||||
// notificationPredicate: (_) => connectivityState.isConnected,
|
|
||||||
// child: CustomScrollView(
|
|
||||||
// key: const PageStorageKey<String>("savedViews"),
|
|
||||||
// slivers: [
|
|
||||||
// SliverOverlapInjector(
|
|
||||||
// handle: searchBarHandle,
|
|
||||||
// ),
|
|
||||||
// SliverOverlapInjector(
|
|
||||||
// handle: savedViewsHandle,
|
|
||||||
// ),
|
|
||||||
// const SavedViewList(),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Widget _buildDocumentsTab(
|
Widget _buildDocumentsTab(
|
||||||
ConnectivityState connectivityState,
|
ConnectivityState connectivityState,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@@ -376,12 +301,11 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
_savedViewsExpansionController.collapse();
|
_savedViewsExpansionController.collapse();
|
||||||
}
|
}
|
||||||
|
|
||||||
final currState = context.read<DocumentsCubit>().state;
|
|
||||||
final max = notification.metrics.maxScrollExtent;
|
final max = notification.metrics.maxScrollExtent;
|
||||||
|
final currentState = context.read<DocumentsCubit>().state;
|
||||||
if (max == 0 ||
|
if (max == 0 ||
|
||||||
_currentTab != 0 ||
|
currentState.isLoading ||
|
||||||
currState.isLoading ||
|
currentState.isLastPageLoaded) {
|
||||||
currState.isLastPageLoaded) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +326,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
},
|
},
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
edgeOffset: kTextTabBarHeight + 2,
|
edgeOffset: kTextTabBarHeight + 2,
|
||||||
onRefresh: _onReloadDocuments,
|
onRefresh: _reloadData,
|
||||||
notificationPredicate: (_) => connectivityState.isConnected,
|
notificationPredicate: (_) => connectivityState.isConnected,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
key: const PageStorageKey<String>("documents"),
|
key: const PageStorageKey<String>("documents"),
|
||||||
@@ -428,8 +352,8 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
},
|
},
|
||||||
onUpdateView: (view) async {
|
onUpdateView: (view) async {
|
||||||
await context.read<SavedViewCubit>().update(view);
|
await context.read<SavedViewCubit>().update(view);
|
||||||
showSnackBar(context,
|
showSnackBar(
|
||||||
"Saved view successfully updated."); //TODO: INTL
|
context, S.of(context)!.savedViewSuccessfullyUpdated);
|
||||||
},
|
},
|
||||||
onDeleteView: (view) async {
|
onDeleteView: (view) async {
|
||||||
HapticFeedback.mediumImpact();
|
HapticFeedback.mediumImpact();
|
||||||
@@ -496,7 +420,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -515,18 +439,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCreateSavedView(DocumentFilter filter) async {
|
|
||||||
//TODO: Implement
|
|
||||||
// final newView = await pushAddSavedViewRoute(context, filter: filter);
|
|
||||||
// if (newView != null) {
|
|
||||||
// try {
|
|
||||||
// await context.read<SavedViewCubit>().add(newView);
|
|
||||||
// } on PaperlessApiException catch (error, stackTrace) {
|
|
||||||
// showErrorMessage(context, error, stackTrace);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
void _openDocumentFilter() async {
|
void _openDocumentFilter() async {
|
||||||
final draggableSheetController = DraggableScrollableController();
|
final draggableSheetController = DraggableScrollableController();
|
||||||
final filterIntent = await showModalBottomSheet<DocumentFilterIntent>(
|
final filterIntent = await showModalBottomSheet<DocumentFilterIntent>(
|
||||||
@@ -717,66 +629,46 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onReloadDocuments() async {
|
///
|
||||||
try {
|
/// Resets the current filter and scrolls all the way to the top of the view.
|
||||||
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
/// If a saved view is currently selected and the filter has changed,
|
||||||
await Future.wait([
|
/// the user will be shown a dialog informing them about the changes.
|
||||||
context.read<DocumentsCubit>().reload(),
|
/// The user can then decide whether to abort the reset or to continue and discard the changes.
|
||||||
context.read<SavedViewCubit>().reload(),
|
|
||||||
]);
|
|
||||||
} on PaperlessApiException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onResetFilter() async {
|
Future<void> _onResetFilter() async {
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
final savedViewCubit = context.read<SavedViewCubit>();
|
final savedViewCubit = context.read<SavedViewCubit>();
|
||||||
final activeView = savedViewCubit.state.maybeMap(
|
|
||||||
|
void toTop() async {
|
||||||
|
await _nestedScrollViewKey.currentState?.outerController.animateTo(
|
||||||
|
0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final activeView = savedViewCubit.state.mapOrNull(
|
||||||
loaded: (state) {
|
loaded: (state) {
|
||||||
if (cubit.state.filter.selectedView != null) {
|
if (cubit.state.filter.selectedView != null) {
|
||||||
return state.savedViews[cubit.state.filter.selectedView!];
|
return state.savedViews[cubit.state.filter.selectedView!];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
orElse: () => null,
|
|
||||||
);
|
);
|
||||||
final viewHasChanged = activeView != null &&
|
final viewHasChanged = activeView != null &&
|
||||||
activeView.toDocumentFilter() != cubit.state.filter;
|
activeView.toDocumentFilter() != cubit.state.filter;
|
||||||
if (viewHasChanged) {
|
if (viewHasChanged) {
|
||||||
final discardChanges = await showDialog(
|
final discardChanges = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const SavedViewChangedDialog(),
|
builder: (context) => const SavedViewChangedDialog(),
|
||||||
);
|
) ??
|
||||||
if (discardChanges == true) {
|
false;
|
||||||
|
if (discardChanges) {
|
||||||
cubit.resetFilter();
|
cubit.resetFilter();
|
||||||
// Reset
|
toTop();
|
||||||
} else if (discardChanges == false) {
|
|
||||||
_updateCurrentSavedView();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cubit.resetFilter();
|
cubit.resetFilter();
|
||||||
|
toTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateCurrentSavedView() async {
|
|
||||||
final savedViewCubit = context.read<SavedViewCubit>();
|
|
||||||
final cubit = context.read<DocumentsCubit>();
|
|
||||||
final activeView = savedViewCubit.state.maybeMap(
|
|
||||||
loaded: (state) {
|
|
||||||
if (cubit.state.filter.selectedView != null) {
|
|
||||||
return state.savedViews[cubit.state.filter.selectedView!];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (activeView == null) return;
|
|
||||||
final newView = activeView.copyWith(
|
|
||||||
filterRules: FilterRule.fromFilter(cubit.state.filter),
|
|
||||||
);
|
|
||||||
|
|
||||||
await savedViewCubit.update(newView);
|
|
||||||
showSnackBar(context, "Saved view successfully updated.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
@@ -8,6 +8,7 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
|||||||
class DocumentsEmptyState extends StatelessWidget {
|
class DocumentsEmptyState extends StatelessWidget {
|
||||||
final DocumentPagingState state;
|
final DocumentPagingState state;
|
||||||
final VoidCallback? onReset;
|
final VoidCallback? onReset;
|
||||||
|
|
||||||
const DocumentsEmptyState({
|
const DocumentsEmptyState({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.state,
|
required this.state,
|
||||||
@@ -17,18 +18,24 @@ class DocumentsEmptyState extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: EmptyState(
|
child: Column(
|
||||||
title: S.of(context)!.oops,
|
children: [
|
||||||
subtitle: S.of(context)!.thereSeemsToBeNothingHere,
|
Text(
|
||||||
bottomChild: state.filter != DocumentFilter.initial && onReset != null
|
S.of(context)!.noDocumentsFound,
|
||||||
? TextButton(
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
onPressed: onReset,
|
),
|
||||||
child: Text(
|
if (state.filter != DocumentFilter.initial && onReset != null)
|
||||||
S.of(context)!.resetFilter,
|
TextButton(
|
||||||
),
|
onPressed: () {
|
||||||
).padded()
|
HapticFeedback.mediumImpact();
|
||||||
: null,
|
onReset!();
|
||||||
),
|
},
|
||||||
|
child: Text(
|
||||||
|
S.of(context)!.resetFilter,
|
||||||
|
),
|
||||||
|
).padded(),
|
||||||
|
],
|
||||||
|
).padded(24),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,11 @@ class SavedViewChangedDialog extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text("Discard changes?"), //TODO: INTL
|
title: Text(S.of(context)!.discardChanges),
|
||||||
content: Text(
|
content: Text(S.of(context)!.savedViewChangedDialogContent),
|
||||||
"Some filters of the currently active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?", //TODO: INTL
|
|
||||||
),
|
|
||||||
actionsOverflowButtonSpacing: 8,
|
actionsOverflowButtonSpacing: 8,
|
||||||
actions: [
|
actions: [
|
||||||
const DialogCancelButton(),
|
const DialogCancelButton(),
|
||||||
// TextButton(
|
|
||||||
// child: Text(S.of(context)!.saveChanges),
|
|
||||||
// onPressed: () {
|
|
||||||
// Navigator.pop(context, false);
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
DialogConfirmButton(
|
DialogConfirmButton(
|
||||||
label: S.of(context)!.resetFilter,
|
label: S.of(context)!.resetFilter,
|
||||||
style: DialogConfirmButtonStyle.danger,
|
style: DialogConfirmButtonStyle.danger,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:math';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
|
||||||
import 'package:paperless_mobile/routes/typed/branches/saved_views_route.dart';
|
import 'package:paperless_mobile/routes/typed/branches/saved_views_route.dart';
|
||||||
|
|
||||||
class SavedViewChip extends StatefulWidget {
|
class SavedViewChip extends StatefulWidget {
|
||||||
@@ -102,7 +101,6 @@ class _SavedViewChipState extends State<SavedViewChip>
|
|||||||
_buildLabel(context, effectiveForegroundColor)
|
_buildLabel(context, effectiveForegroundColor)
|
||||||
.paddedSymmetrically(
|
.paddedSymmetrically(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
vertical: 0,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedOnly(left: 8),
|
).paddedOnly(left: 8),
|
||||||
@@ -120,6 +118,7 @@ class _SavedViewChipState extends State<SavedViewChip>
|
|||||||
|
|
||||||
Widget _buildTrailing(Color effectiveForegroundColor) {
|
Widget _buildTrailing(Color effectiveForegroundColor) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
icon: AnimatedBuilder(
|
icon: AnimatedBuilder(
|
||||||
animation: _animation,
|
animation: _animation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_view_chip.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_view_chip.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
@@ -46,99 +46,185 @@ class _SavedViewsWidgetState extends State<SavedViewsWidget>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PageStorage(
|
return BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||||
bucket: PageStorageBucket(),
|
builder: (context, state) {
|
||||||
child: ExpansionTile(
|
final selectedView = state.mapOrNull(
|
||||||
controller: widget.controller,
|
loaded: (value) {
|
||||||
tilePadding: const EdgeInsets.only(left: 8),
|
if (widget.filter.selectedView != null) {
|
||||||
trailing: RotationTransition(
|
return value.savedViews[widget.filter.selectedView!];
|
||||||
turns: _animation,
|
}
|
||||||
child: const Icon(Icons.expand_more),
|
},
|
||||||
).paddedOnly(right: 8),
|
);
|
||||||
onExpansionChanged: (isExpanded) {
|
final selectedViewHasChanged = selectedView != null &&
|
||||||
if (isExpanded) {
|
selectedView.toDocumentFilter() != widget.filter;
|
||||||
_animationController.forward();
|
return PageStorage(
|
||||||
} else {
|
bucket: PageStorageBucket(),
|
||||||
_animationController.reverse().then((value) => setState(() {}));
|
child: ExpansionTile(
|
||||||
}
|
controller: widget.controller,
|
||||||
},
|
tilePadding: const EdgeInsets.only(left: 8),
|
||||||
title: Text(
|
trailing: RotationTransition(
|
||||||
S.of(context)!.views,
|
turns: _animation,
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
child: const Icon(Icons.expand_more),
|
||||||
),
|
).paddedOnly(right: 8),
|
||||||
leading: Icon(
|
onExpansionChanged: (isExpanded) {
|
||||||
Icons.saved_search,
|
if (isExpanded) {
|
||||||
color: Theme.of(context).colorScheme.primary,
|
_animationController.forward();
|
||||||
).padded(),
|
} else {
|
||||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
_animationController.reverse().then((value) => setState(() {}));
|
||||||
children: [
|
}
|
||||||
BlocBuilder<SavedViewCubit, SavedViewState>(
|
},
|
||||||
builder: (context, state) {
|
title: Row(
|
||||||
return state.map(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
initial: (_) => const Placeholder(),
|
children: [
|
||||||
loading: (_) => const Placeholder(),
|
Flexible(
|
||||||
loaded: (value) {
|
child: Column(
|
||||||
if (value.savedViews.isEmpty) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
return Text(S.of(context)!.noItemsFound)
|
children: [
|
||||||
.paddedOnly(left: 16);
|
Text(
|
||||||
}
|
S.of(context)!.views,
|
||||||
return Container(
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
margin: EdgeInsets.only(top: 16),
|
|
||||||
height: kMinInteractiveDimension,
|
|
||||||
child: NotificationListener<ScrollNotification>(
|
|
||||||
onNotification: (notification) => true,
|
|
||||||
child: CustomScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
slivers: [
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(width: 12),
|
|
||||||
),
|
|
||||||
SliverList.separated(
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final view =
|
|
||||||
value.savedViews.values.elementAt(index);
|
|
||||||
final isSelected =
|
|
||||||
(widget.filter.selectedView ?? -1) == view.id;
|
|
||||||
return SavedViewChip(
|
|
||||||
view: view,
|
|
||||||
onViewSelected: widget.onViewSelected,
|
|
||||||
selected: isSelected,
|
|
||||||
hasChanged: isSelected &&
|
|
||||||
view.toDocumentFilter() != widget.filter,
|
|
||||||
onUpdateView: widget.onUpdateView,
|
|
||||||
onDeleteView: widget.onDeleteView,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
itemCount: value.savedViews.length,
|
|
||||||
),
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(width: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
if (selectedView != null)
|
||||||
|
Text(
|
||||||
|
selectedView.name,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onBackground
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedScale(
|
||||||
|
scale: selectedViewHasChanged ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
final newView = selectedView!.copyWith(
|
||||||
|
filterRules: FilterRule.fromFilter(widget.filter),
|
||||||
|
);
|
||||||
|
widget.onUpdateView(newView);
|
||||||
|
},
|
||||||
|
child: Text(S.of(context)!.saveChanges),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: Icon(
|
||||||
|
Icons.saved_search,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
).padded(),
|
||||||
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
state
|
||||||
|
.maybeMap(
|
||||||
|
loaded: (value) {
|
||||||
|
if (value.savedViews.isEmpty) {
|
||||||
|
return Text(S.of(context)!.noItemsFound)
|
||||||
|
.paddedOnly(left: 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: kMinInteractiveDimension,
|
||||||
|
child: NotificationListener<ScrollNotification>(
|
||||||
|
onNotification: (notification) => true,
|
||||||
|
child: CustomScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
slivers: [
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 12),
|
||||||
|
),
|
||||||
|
SliverList.separated(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final view =
|
||||||
|
value.savedViews.values.elementAt(index);
|
||||||
|
final isSelected =
|
||||||
|
(widget.filter.selectedView ?? -1) ==
|
||||||
|
view.id;
|
||||||
|
return SavedViewChip(
|
||||||
|
view: view,
|
||||||
|
onViewSelected: widget.onViewSelected,
|
||||||
|
selected: isSelected,
|
||||||
|
hasChanged: isSelected &&
|
||||||
|
view.toDocumentFilter() !=
|
||||||
|
widget.filter,
|
||||||
|
onUpdateView: widget.onUpdateView,
|
||||||
|
onDeleteView: widget.onDeleteView,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
itemCount: value.savedViews.length,
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error: (_) => Text(S.of(context)!.couldNotLoadSavedViews)
|
||||||
|
.paddedOnly(left: 16),
|
||||||
|
orElse: _buildLoadingState,
|
||||||
|
)
|
||||||
|
.paddedOnly(top: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Tooltip(
|
||||||
|
message: S.of(context)!.createFromCurrentFilter,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
CreateSavedViewRoute(widget.filter).push(context);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(S.of(context)!.newView),
|
||||||
|
),
|
||||||
|
).padded(4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLoadingState() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16),
|
||||||
|
height: kMinInteractiveDimension,
|
||||||
|
child: NotificationListener<ScrollNotification>(
|
||||||
|
onNotification: (notification) => true,
|
||||||
|
child: ShimmerPlaceholder(
|
||||||
|
child: CustomScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
slivers: [
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 12),
|
||||||
|
),
|
||||||
|
SliverList.separated(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
width: 130,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
error: (_) => const Placeholder(),
|
separatorBuilder: (context, index) => const SizedBox(width: 8),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Tooltip(
|
|
||||||
message: "Create from current filter", //TODO: INTL
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
CreateSavedViewRoute(widget.filter).push(context);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: Text(S.of(context)!.newView),
|
|
||||||
),
|
),
|
||||||
).padded(4),
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import 'dart:developer';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
|
||||||
class EditLabelPage<T extends Label> extends StatelessWidget {
|
class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||||
@@ -56,8 +57,9 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
|||||||
final Future<T> Function(BuildContext context, T label) onSubmit;
|
final Future<T> Function(BuildContext context, T label) onSubmit;
|
||||||
final Future<void> Function(BuildContext context, T label) onDelete;
|
final Future<void> Function(BuildContext context, T label) onDelete;
|
||||||
final bool canDelete;
|
final bool canDelete;
|
||||||
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
const EditLabelForm({
|
EditLabelForm({
|
||||||
super.key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.fromJsonT,
|
required this.fromJsonT,
|
||||||
@@ -69,26 +71,32 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return PopWithUnsavedChanges(
|
||||||
appBar: AppBar(
|
hasChangesPredicate: () {
|
||||||
title: Text(S.of(context)!.edit),
|
return _formKey.currentState?.isDirty ?? false;
|
||||||
actions: [
|
},
|
||||||
IconButton(
|
child: Scaffold(
|
||||||
onPressed: canDelete ? () => _onDelete(context) : null,
|
appBar: AppBar(
|
||||||
icon: const Icon(Icons.delete),
|
title: Text(S.of(context)!.edit),
|
||||||
),
|
actions: [
|
||||||
],
|
IconButton(
|
||||||
),
|
onPressed: canDelete ? () => _onDelete(context) : null,
|
||||||
body: LabelForm<T>(
|
icon: const Icon(Icons.delete),
|
||||||
autofocusNameField: false,
|
),
|
||||||
initialValue: label,
|
],
|
||||||
fromJsonT: fromJsonT,
|
),
|
||||||
submitButtonConfig: SubmitButtonConfig<T>(
|
body: LabelForm<T>(
|
||||||
icon: const Icon(Icons.save),
|
formKey: _formKey,
|
||||||
label: Text(S.of(context)!.saveChanges),
|
autofocusNameField: false,
|
||||||
onSubmit: (label) => onSubmit(context, label),
|
initialValue: label,
|
||||||
|
fromJsonT: fromJsonT,
|
||||||
|
submitButtonConfig: SubmitButtonConfig<T>(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: Text(S.of(context)!.saveChanges),
|
||||||
|
onSubmit: (label) => onSubmit(context, label),
|
||||||
|
),
|
||||||
|
additionalFields: additionalFields,
|
||||||
),
|
),
|
||||||
additionalFields: additionalFields,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
|
||||||
class SubmitButtonConfig<T extends Label> {
|
class SubmitButtonConfig<T extends Label> {
|
||||||
@@ -36,6 +33,7 @@ class LabelForm<T extends Label> extends StatefulWidget {
|
|||||||
final List<Widget> additionalFields;
|
final List<Widget> additionalFields;
|
||||||
|
|
||||||
final bool autofocusNameField;
|
final bool autofocusNameField;
|
||||||
|
final GlobalKey<FormBuilderState>? formKey;
|
||||||
|
|
||||||
const LabelForm({
|
const LabelForm({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -44,6 +42,7 @@ class LabelForm<T extends Label> extends StatefulWidget {
|
|||||||
this.additionalFields = const [],
|
this.additionalFields = const [],
|
||||||
required this.submitButtonConfig,
|
required this.submitButtonConfig,
|
||||||
required this.autofocusNameField,
|
required this.autofocusNameField,
|
||||||
|
this.formKey,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -51,7 +50,7 @@ class LabelForm<T extends Label> extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
late final GlobalKey<FormBuilderState> _formKey;
|
||||||
|
|
||||||
late bool _enableMatchFormField;
|
late bool _enableMatchFormField;
|
||||||
|
|
||||||
@@ -60,6 +59,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_formKey = widget.formKey ?? GlobalKey<FormBuilderState>();
|
||||||
var matchingAlgorithm = (widget.initialValue?.matchingAlgorithm ??
|
var matchingAlgorithm = (widget.initialValue?.matchingAlgorithm ??
|
||||||
MatchingAlgorithm.defaultValue);
|
MatchingAlgorithm.defaultValue);
|
||||||
_enableMatchFormField = matchingAlgorithm != MatchingAlgorithm.auto &&
|
_enableMatchFormField = matchingAlgorithm != MatchingAlgorithm.auto &&
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
@@ -17,14 +16,9 @@ import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
|||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/routes/typed/branches/landing_route.dart';
|
|
||||||
import 'package:paperless_mobile/routes/typed/top_level/login_route.dart';
|
|
||||||
import 'package:paperless_mobile/routes/typed/top_level/switching_accounts_route.dart';
|
|
||||||
import 'package:paperless_mobile/routes/typed/top_level/verify_identity_route.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HomeShellWidget extends StatelessWidget {
|
class HomeShellWidget extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/theme.dart';
|
||||||
|
|
||||||
const _landingPage = 0;
|
const _landingPage = 0;
|
||||||
const _documentsIndex = 1;
|
const _documentsIndex = 1;
|
||||||
@@ -28,96 +30,105 @@ class ScaffoldWithNavigationBar extends StatefulWidget {
|
|||||||
|
|
||||||
class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
|
class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void didChangeDependencies() {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
@override
|
||||||
drawer: const AppDrawer(),
|
Widget build(BuildContext context) {
|
||||||
bottomNavigationBar: NavigationBar(
|
final theme = Theme.of(context);
|
||||||
selectedIndex: widget.navigationShell.currentIndex,
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
onDestinationSelected: (index) {
|
value: buildOverlayStyle(theme),
|
||||||
widget.navigationShell.goBranch(
|
child: Scaffold(
|
||||||
index,
|
drawer: const AppDrawer(),
|
||||||
initialLocation: index == widget.navigationShell.currentIndex,
|
bottomNavigationBar: NavigationBar(
|
||||||
);
|
elevation: 3,
|
||||||
},
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
destinations: [
|
selectedIndex: widget.navigationShell.currentIndex,
|
||||||
NavigationDestination(
|
onDestinationSelected: (index) {
|
||||||
icon: const Icon(Icons.home_outlined),
|
widget.navigationShell.goBranch(
|
||||||
selectedIcon: Icon(
|
index,
|
||||||
Icons.home,
|
initialLocation: index == widget.navigationShell.currentIndex,
|
||||||
color: primaryColor,
|
);
|
||||||
),
|
},
|
||||||
label: "Home", //TODO: INTL
|
destinations: [
|
||||||
),
|
|
||||||
_toggleDestination(
|
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: const Icon(Icons.description_outlined),
|
icon: const Icon(Icons.home_outlined),
|
||||||
selectedIcon: Icon(
|
selectedIcon: Icon(
|
||||||
Icons.description,
|
Icons.home,
|
||||||
color: primaryColor,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
label: S.of(context)!.documents,
|
label: S.of(context)!.home,
|
||||||
),
|
),
|
||||||
disableWhen: !widget.authenticatedUser.canViewDocuments,
|
_toggleDestination(
|
||||||
),
|
NavigationDestination(
|
||||||
_toggleDestination(
|
icon: const Icon(Icons.description_outlined),
|
||||||
NavigationDestination(
|
selectedIcon: Icon(
|
||||||
icon: const Icon(Icons.document_scanner_outlined),
|
Icons.description,
|
||||||
selectedIcon: Icon(
|
color: theme.colorScheme.primary,
|
||||||
Icons.document_scanner,
|
),
|
||||||
color: primaryColor,
|
label: S.of(context)!.documents,
|
||||||
),
|
),
|
||||||
label: S.of(context)!.scanner,
|
disableWhen: !widget.authenticatedUser.canViewDocuments,
|
||||||
),
|
),
|
||||||
disableWhen: !widget.authenticatedUser.canCreateDocuments,
|
_toggleDestination(
|
||||||
),
|
NavigationDestination(
|
||||||
_toggleDestination(
|
icon: const Icon(Icons.document_scanner_outlined),
|
||||||
NavigationDestination(
|
selectedIcon: Icon(
|
||||||
icon: const Icon(Icons.sell_outlined),
|
Icons.document_scanner,
|
||||||
selectedIcon: Icon(
|
color: theme.colorScheme.primary,
|
||||||
Icons.sell,
|
),
|
||||||
color: primaryColor,
|
label: S.of(context)!.scanner,
|
||||||
),
|
),
|
||||||
label: S.of(context)!.labels,
|
disableWhen: !widget.authenticatedUser.canCreateDocuments,
|
||||||
),
|
),
|
||||||
disableWhen: !widget.authenticatedUser.canViewAnyLabel,
|
_toggleDestination(
|
||||||
),
|
NavigationDestination(
|
||||||
_toggleDestination(
|
icon: const Icon(Icons.sell_outlined),
|
||||||
NavigationDestination(
|
selectedIcon: Icon(
|
||||||
icon: Builder(
|
Icons.sell,
|
||||||
builder: (context) {
|
color: theme.colorScheme.primary,
|
||||||
return BlocBuilder<InboxCubit, InboxState>(
|
),
|
||||||
builder: (context, state) {
|
label: S.of(context)!.labels,
|
||||||
return Badge.count(
|
|
||||||
isLabelVisible: state.itemsInInboxCount > 0,
|
|
||||||
count: state.itemsInInboxCount,
|
|
||||||
child: const Icon(Icons.inbox_outlined),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
selectedIcon: BlocBuilder<InboxCubit, InboxState>(
|
disableWhen: !widget.authenticatedUser.canViewAnyLabel,
|
||||||
builder: (context, state) {
|
|
||||||
return Badge.count(
|
|
||||||
isLabelVisible: state.itemsInInboxCount > 0 &&
|
|
||||||
widget.authenticatedUser.canViewInbox,
|
|
||||||
count: state.itemsInInboxCount,
|
|
||||||
child: Icon(
|
|
||||||
Icons.inbox,
|
|
||||||
color: primaryColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
label: S.of(context)!.inbox,
|
|
||||||
),
|
),
|
||||||
disableWhen: !widget.authenticatedUser.canViewInbox,
|
_toggleDestination(
|
||||||
),
|
NavigationDestination(
|
||||||
],
|
icon: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return BlocBuilder<InboxCubit, InboxState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Badge.count(
|
||||||
|
isLabelVisible: state.itemsInInboxCount > 0,
|
||||||
|
count: state.itemsInInboxCount,
|
||||||
|
child: const Icon(Icons.inbox_outlined),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
selectedIcon: BlocBuilder<InboxCubit, InboxState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Badge.count(
|
||||||
|
isLabelVisible: state.itemsInInboxCount > 0 &&
|
||||||
|
widget.authenticatedUser.canViewInbox,
|
||||||
|
count: state.itemsInInboxCount,
|
||||||
|
child: Icon(
|
||||||
|
Icons.inbox,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
label: S.of(context)!.inbox,
|
||||||
|
),
|
||||||
|
disableWhen: !widget.authenticatedUser.canViewInbox,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: widget.navigationShell,
|
||||||
),
|
),
|
||||||
body: widget.navigationShell,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,40 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
|
final _nestedScrollViewKey = GlobalKey<NestedScrollViewState>();
|
||||||
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
|
bool _showExtendedFab = true;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_nestedScrollViewKey.currentState!.innerController
|
||||||
|
.addListener(_scrollExtentChangedListener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nestedScrollViewKey.currentState?.innerController
|
||||||
|
.removeListener(_scrollExtentChangedListener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scrollExtentChangedListener() {
|
||||||
|
const threshold = 400;
|
||||||
|
final offset =
|
||||||
|
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
||||||
|
if (offset < threshold && _showExtendedFab == false) {
|
||||||
|
setState(() {
|
||||||
|
_showExtendedFab = true;
|
||||||
|
});
|
||||||
|
} else if (offset >= threshold && _showExtendedFab == true) {
|
||||||
|
setState(() {
|
||||||
|
_showExtendedFab = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -48,9 +80,31 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return FloatingActionButton.extended(
|
return FloatingActionButton.extended(
|
||||||
heroTag: "fab_inbox",
|
extendedPadding: _showExtendedFab
|
||||||
label: Text(S.of(context)!.allSeen),
|
? null
|
||||||
icon: const Icon(Icons.done_all),
|
: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
heroTag: "inbox_page_fab",
|
||||||
|
label: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
transitionBuilder: (child, animation) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: SizeTransition(
|
||||||
|
sizeFactor: animation,
|
||||||
|
axis: Axis.horizontal,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _showExtendedFab
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.done_all),
|
||||||
|
Text(S.of(context)!.allSeen),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Icon(Icons.done_all),
|
||||||
|
),
|
||||||
onPressed: state.hasLoaded && state.documents.isNotEmpty
|
onPressed: state.hasLoaded && state.documents.isNotEmpty
|
||||||
? () => _onMarkAllAsSeen(
|
? () => _onMarkAllAsSeen(
|
||||||
state.documents,
|
state.documents,
|
||||||
@@ -63,13 +117,9 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: true,
|
top: true,
|
||||||
child: NestedScrollView(
|
child: NestedScrollView(
|
||||||
|
key: _nestedScrollViewKey,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverOverlapAbsorber(
|
SliverSearchBar(titleText: S.of(context)!.inbox),
|
||||||
handle: searchBarHandle,
|
|
||||||
sliver: SliverSearchBar(
|
|
||||||
titleText: S.of(context)!.inbox,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
body: BlocBuilder<InboxCubit, InboxState>(
|
body: BlocBuilder<InboxCubit, InboxState>(
|
||||||
builder: (_, state) {
|
builder: (_, state) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:paperless_api/paperless_api.dart';
|
|||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
||||||
|
|
||||||
part 'label_state.dart';
|
|
||||||
part 'label_cubit.freezed.dart';
|
part 'label_cubit.freezed.dart';
|
||||||
|
part 'label_state.dart';
|
||||||
|
|
||||||
class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
|
class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
|
||||||
@override
|
@override
|
||||||
@@ -25,6 +25,15 @@ class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> reload() {
|
||||||
|
return Future.wait([
|
||||||
|
labelRepository.findAllCorrespondents(),
|
||||||
|
labelRepository.findAllDocumentTypes(),
|
||||||
|
labelRepository.findAllTags(),
|
||||||
|
labelRepository.findAllStoragePaths(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
labelRepository.removeListener(this);
|
labelRepository.removeListener(this);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
SliverOverlapAbsorberHandle();
|
SliverOverlapAbsorberHandle();
|
||||||
|
|
||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
||||||
|
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
|
||||||
int _calculateTabCount(UserModel user) => [
|
int _calculateTabCount(UserModel user) => [
|
||||||
@@ -48,6 +49,12 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
@@ -65,8 +72,17 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
heroTag: "fab_labels_page",
|
heroTag: "inbox_page_fab",
|
||||||
|
label: Text(
|
||||||
|
[
|
||||||
|
S.of(context)!.addCorrespondent,
|
||||||
|
S.of(context)!.addDocumentType,
|
||||||
|
S.of(context)!.addTag,
|
||||||
|
S.of(context)!.addStoragePath,
|
||||||
|
][_currentIndex],
|
||||||
|
),
|
||||||
|
icon: Icon(Icons.add),
|
||||||
onPressed: [
|
onPressed: [
|
||||||
if (user.canViewCorrespondents)
|
if (user.canViewCorrespondents)
|
||||||
() => CreateLabelRoute(LabelType.correspondent)
|
() => CreateLabelRoute(LabelType.correspondent)
|
||||||
@@ -80,7 +96,6 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
() => CreateLabelRoute(LabelType.storagePath)
|
() => CreateLabelRoute(LabelType.storagePath)
|
||||||
.push(context),
|
.push(context),
|
||||||
][_currentIndex],
|
][_currentIndex],
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
),
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
floatHeaderSlivers: true,
|
floatHeaderSlivers: true,
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Welcome, ${currentUser.fullName ?? currentUser.username}!",
|
S.of(context)!.welcomeUser(
|
||||||
|
currentUser.fullName ?? currentUser.username),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@@ -81,13 +82,12 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(S.of(context)!.noSavedViewOnHomepageHint)
|
||||||
"There are no saved views to show on your dashboard.", //TODO: INTL
|
.padded(),
|
||||||
).padded(),
|
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: Text("Add new view"),
|
label: Text(S.of(context)!.newView),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
).paddedOnly(left: 16),
|
).paddedOnly(left: 16),
|
||||||
@@ -121,35 +121,23 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
|
|
||||||
Widget _buildStatisticsCard(BuildContext context) {
|
Widget _buildStatisticsCard(BuildContext context) {
|
||||||
final currentUser = context.read<LocalUserAccount>().paperlessUser;
|
final currentUser = context.read<LocalUserAccount>().paperlessUser;
|
||||||
return FutureBuilder<PaperlessServerStatisticsModel>(
|
|
||||||
future: context.read<PaperlessServerStatsApi>().getServerStatistics(),
|
return ExpansionCard(
|
||||||
builder: (context, snapshot) {
|
initiallyExpanded: false,
|
||||||
if (!snapshot.hasData) {
|
title: Text(
|
||||||
return Card(
|
S.of(context)!.statistics,
|
||||||
margin: const EdgeInsets.all(16),
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
content: FutureBuilder<PaperlessServerStatisticsModel>(
|
||||||
children: [
|
future: context.read<PaperlessServerStatsApi>().getServerStatistics(),
|
||||||
Text(
|
builder: (context, snapshot) {
|
||||||
"Statistics", //TODO: INTL
|
if (!snapshot.hasData) {
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
return const Center(
|
||||||
),
|
child: CircularProgressIndicator(),
|
||||||
const Padding(
|
).paddedOnly(top: 8, bottom: 24);
|
||||||
padding: EdgeInsets.all(16.0),
|
}
|
||||||
child: Center(child: CircularProgressIndicator()),
|
final stats = snapshot.data!;
|
||||||
),
|
return Column(
|
||||||
],
|
|
||||||
).padded(16),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final stats = snapshot.data!;
|
|
||||||
return ExpansionCard(
|
|
||||||
initiallyExpanded: false,
|
|
||||||
title: Text(
|
|
||||||
"Statistics", //TODO: INTL
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
@@ -157,7 +145,7 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
shape: Theme.of(context).cardTheme.shape,
|
shape: Theme.of(context).cardTheme.shape,
|
||||||
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
||||||
title: const Text("Documents in inbox:"),
|
title: Text(S.of(context)!.documentsInInbox),
|
||||||
onTap: currentUser.canViewTags && currentUser.canViewDocuments
|
onTap: currentUser.canViewTags && currentUser.canViewDocuments
|
||||||
? () => InboxRoute().go(context)
|
? () => InboxRoute().go(context)
|
||||||
: null,
|
: null,
|
||||||
@@ -172,19 +160,13 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
shape: Theme.of(context).cardTheme.shape,
|
shape: Theme.of(context).cardTheme.shape,
|
||||||
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
||||||
title: const Text("Total documents:"),
|
title: Text(S.of(context)!.totalDocuments),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
DocumentsRoute().go(context);
|
DocumentsRoute().go(context);
|
||||||
},
|
},
|
||||||
trailing: Chip(
|
trailing: Text(
|
||||||
padding: const EdgeInsets.symmetric(
|
stats.documentsTotal.toString(),
|
||||||
horizontal: 4,
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 4),
|
|
||||||
label: Text(
|
|
||||||
stats.documentsTotal.toString(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -193,16 +175,10 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
shape: Theme.of(context).cardTheme.shape,
|
shape: Theme.of(context).cardTheme.shape,
|
||||||
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
titleTextStyle: Theme.of(context).textTheme.labelLarge,
|
||||||
title: const Text("Total characters:"),
|
title: Text(S.of(context)!.totalCharacters),
|
||||||
trailing: Chip(
|
trailing: Text(
|
||||||
padding: const EdgeInsets.symmetric(
|
stats.totalChars.toString(),
|
||||||
horizontal: 4,
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 4),
|
|
||||||
label: Text(
|
|
||||||
stats.totalChars.toString(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -214,9 +190,9 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padded(16),
|
).padded(16);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class ExpansionCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
child: Theme(
|
child: Theme(
|
||||||
@@ -29,8 +30,17 @@ class ExpansionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
backgroundColor: ElevationOverlay.applySurfaceTint(
|
||||||
|
colorScheme.surface,
|
||||||
|
colorScheme.surfaceTint,
|
||||||
|
4,
|
||||||
|
),
|
||||||
initiallyExpanded: initiallyExpanded,
|
initiallyExpanded: initiallyExpanded,
|
||||||
|
collapsedBackgroundColor: ElevationOverlay.applySurfaceTint(
|
||||||
|
colorScheme.surface,
|
||||||
|
colorScheme.surfaceTint,
|
||||||
|
4,
|
||||||
|
),
|
||||||
title: title,
|
title: title,
|
||||||
children: [content],
|
children: [content],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:paperless_mobile/core/security/session_manager.dart';
|
|||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
part 'authentication_cubit.freezed.dart';
|
part 'authentication_cubit.freezed.dart';
|
||||||
part 'authentication_state.dart';
|
part 'authentication_state.dart';
|
||||||
@@ -196,8 +197,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
"restoreSessionState",
|
"restoreSessionState",
|
||||||
"Biometric authentication required, waiting for user to authenticate...",
|
"Biometric authentication required, waiting for user to authenticate...",
|
||||||
);
|
);
|
||||||
final localAuthSuccess = await _localAuthService
|
final authenticationMesage =
|
||||||
.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
|
(await S.delegate.load(Locale(globalSettings.preferredLocaleSubtag)))
|
||||||
|
.verifyYourIdentity;
|
||||||
|
final localAuthSuccess =
|
||||||
|
await _localAuthService.authenticateLocalUser(authenticationMesage);
|
||||||
if (!localAuthSuccess) {
|
if (!localAuthSuccess) {
|
||||||
emit(const AuthenticationState.requriresLocalAuthentication());
|
emit(const AuthenticationState.requriresLocalAuthentication());
|
||||||
_debugPrintMessage(
|
_debugPrintMessage(
|
||||||
@@ -233,7 +237,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
);
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"User should be authenticated but no authentication information was found.",
|
"User should be authenticated but no authentication information was found.",
|
||||||
); //TODO: INTL
|
);
|
||||||
}
|
}
|
||||||
_debugPrintMessage(
|
_debugPrintMessage(
|
||||||
"restoreSessionState",
|
"restoreSessionState",
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class LocalNotificationService {
|
|||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
),
|
),
|
||||||
); //TODO: INTL
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
|
|
||||||
part 'saved_view_state.dart';
|
|
||||||
part 'saved_view_cubit.freezed.dart';
|
part 'saved_view_cubit.freezed.dart';
|
||||||
|
part 'saved_view_state.dart';
|
||||||
|
|
||||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||||
final SavedViewRepository _savedViewRepository;
|
final SavedViewRepository _savedViewRepository;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -28,53 +28,57 @@ class SavedViewPreview extends StatelessWidget {
|
|||||||
return ExpansionCard(
|
return ExpansionCard(
|
||||||
initiallyExpanded: expanded,
|
initiallyExpanded: expanded,
|
||||||
title: Text(savedView.name),
|
title: Text(savedView.name),
|
||||||
content: BlocBuilder<SavedViewPreviewCubit, SavedViewPreviewState>(
|
content: Column(
|
||||||
builder: (context, state) {
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
return state.maybeWhen(
|
children: [
|
||||||
loaded: (documents) {
|
BlocBuilder<SavedViewPreviewCubit, SavedViewPreviewState>(
|
||||||
return Column(
|
builder: (context, state) {
|
||||||
children: [
|
return state.maybeWhen(
|
||||||
if (documents.isEmpty)
|
loaded: (documents) {
|
||||||
Text("This view does not match any documents.").padded()
|
if (documents.isEmpty) {
|
||||||
else
|
return Text(S.of(context)!.noDocumentsFound).padded();
|
||||||
for (final document in documents)
|
} else {
|
||||||
DocumentListItem(
|
return Column(
|
||||||
document: document,
|
children: [
|
||||||
isLabelClickable: false,
|
for (final document in documents)
|
||||||
isSelected: false,
|
DocumentListItem(
|
||||||
isSelectionActive: false,
|
document: document,
|
||||||
onTap: (document) {
|
isLabelClickable: false,
|
||||||
DocumentDetailsRoute($extra: document)
|
isSelected: false,
|
||||||
.push(context);
|
isSelectionActive: false,
|
||||||
},
|
onTap: (document) {
|
||||||
onSelected: null,
|
DocumentDetailsRoute($extra: document)
|
||||||
),
|
.push(context);
|
||||||
Row(
|
},
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
onSelected: null,
|
||||||
children: [
|
),
|
||||||
TextButton.icon(
|
],
|
||||||
icon: const Icon(Icons.open_in_new),
|
);
|
||||||
label: Text("Show all"), //TODO: INTL
|
}
|
||||||
onPressed: () {
|
},
|
||||||
context.read<DocumentsCubit>().updateFilter(
|
error: () => Text(S.of(context)!.couldNotLoadSavedViews),
|
||||||
filter: savedView.toDocumentFilter(),
|
orElse: () => const Center(
|
||||||
);
|
child: CircularProgressIndicator(),
|
||||||
DocumentsRoute().go(context);
|
).paddedOnly(top: 8, bottom: 24),
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
error: () =>
|
),
|
||||||
const Text("Could not load saved view."), //TODO: INTL
|
Row(
|
||||||
orElse: () => const Padding(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
padding: EdgeInsets.all(8.0),
|
children: [
|
||||||
child: Center(child: CircularProgressIndicator()),
|
TextButton.icon(
|
||||||
),
|
icon: const Icon(Icons.open_in_new),
|
||||||
);
|
label: Text(S.of(context)!.showAll),
|
||||||
},
|
onPressed: () {
|
||||||
|
context.read<DocumentsCubit>().updateFilter(
|
||||||
|
filter: savedView.toDocumentFilter(),
|
||||||
|
);
|
||||||
|
DocumentsRoute().go(context);
|
||||||
|
},
|
||||||
|
).paddedOnly(bottom: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_mobile/constants.dart';
|
import 'package:paperless_mobile/constants.dart';
|
||||||
import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:paperless_mobile/features/settings/model/color_scheme_option.dar
|
|||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/theme.dart';
|
||||||
|
|
||||||
class ColorSchemeOptionSetting extends StatelessWidget {
|
class ColorSchemeOptionSetting extends StatelessWidget {
|
||||||
const ColorSchemeOptionSetting({super.key});
|
const ColorSchemeOptionSetting({super.key});
|
||||||
@@ -52,10 +54,10 @@ class ColorSchemeOptionSetting extends StatelessWidget {
|
|||||||
initialValue: settings.preferredColorSchemeOption,
|
initialValue: settings.preferredColorSchemeOption,
|
||||||
),
|
),
|
||||||
).then(
|
).then(
|
||||||
(value) {
|
(value) async {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
settings.preferredColorSchemeOption = value;
|
settings.preferredColorSchemeOption = value;
|
||||||
settings.save();
|
await settings.save();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/theme.dart';
|
||||||
|
|
||||||
class ThemeModeSetting extends StatelessWidget {
|
class ThemeModeSetting extends StatelessWidget {
|
||||||
const ThemeModeSetting({super.key});
|
const ThemeModeSetting({super.key});
|
||||||
@@ -34,10 +36,10 @@ class ThemeModeSetting extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).then((value) {
|
).then((value) async {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
settings.preferredThemeMode = value;
|
settings.preferredThemeMode = value;
|
||||||
settings.save();
|
await settings.save();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
"@startTyping": {},
|
"@startTyping": {},
|
||||||
"doYouReallyWantToDeleteThisView": "Vols esborrar aquesta vista?",
|
"doYouReallyWantToDeleteThisView": "Vols esborrar aquesta vista?",
|
||||||
"@doYouReallyWantToDeleteThisView": {},
|
"@doYouReallyWantToDeleteThisView": {},
|
||||||
"deleteView": "Esborra Vista ",
|
"deleteView": "Esborra Vista {name}?",
|
||||||
"@deleteView": {},
|
"@deleteView": {},
|
||||||
"addedAt": "Afegit",
|
"addedAt": "Afegit",
|
||||||
"@addedAt": {},
|
"@addedAt": {},
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"@startTyping": {},
|
"@startTyping": {},
|
||||||
"doYouReallyWantToDeleteThisView": "Opravdu chceš tento náhled smazat?",
|
"doYouReallyWantToDeleteThisView": "Opravdu chceš tento náhled smazat?",
|
||||||
"@doYouReallyWantToDeleteThisView": {},
|
"@doYouReallyWantToDeleteThisView": {},
|
||||||
"deleteView": "Smazat náhled ",
|
"deleteView": "Smazat náhled {name}?",
|
||||||
"@deleteView": {},
|
"@deleteView": {},
|
||||||
"addedAt": "Přidáno",
|
"addedAt": "Přidáno",
|
||||||
"@addedAt": {},
|
"@addedAt": {},
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"@startTyping": {},
|
"@startTyping": {},
|
||||||
"doYouReallyWantToDeleteThisView": "Möchtest Du diese Ansicht wirklich löschen?",
|
"doYouReallyWantToDeleteThisView": "Möchtest Du diese Ansicht wirklich löschen?",
|
||||||
"@doYouReallyWantToDeleteThisView": {},
|
"@doYouReallyWantToDeleteThisView": {},
|
||||||
"deleteView": "Lösche Ansicht ",
|
"deleteView": "Ansicht {name} löschen?",
|
||||||
"@deleteView": {},
|
"@deleteView": {},
|
||||||
"addedAt": "Hinzugefügt am",
|
"addedAt": "Hinzugefügt am",
|
||||||
"@addedAt": {},
|
"@addedAt": {},
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Vielen Dank, dass Du diese App unterstützen möchtest! Aufgrund der Zahlungsrichtlinien von Google und Apple dürfen keine Links, die zu Spendenseiten führen, in der App angezeigt werden. Nicht einmal die Verlinkung zur Repository-Seite des Projekts scheint in diesem Zusammenhang erlaubt zu sein. Werfe von daher vielleicht einen Blick auf den Abschnitt 'Donations' in der README des Projekts. Deine Unterstützung ist sehr willkommen und hält die Entwicklung dieser App am Leben. Vielen Dank!",
|
"donationDialogContent": "Vielen Dank, dass Du diese App unterstützen möchtest! Aufgrund der Zahlungsrichtlinien von Google und Apple dürfen keine Links, die zu Spendenseiten führen, in der App angezeigt werden. Nicht einmal die Verlinkung zur Repository-Seite des Projekts scheint in diesem Zusammenhang erlaubt zu sein. Werfe von daher vielleicht einen Blick auf den Abschnitt 'Donations' in der README des Projekts. Deine Unterstützung ist sehr willkommen und hält die Entwicklung dieser App am Leben. Vielen Dank!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "Keine Dokumente gefunden.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Korrespondent konnte nicht gelöscht werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Dokumenttyp konnten nicht gelöscht werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Tag konnte nicht gelöscht werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Speicherpfad konnte nicht gelöscht werden, bitte versuchen Sie es erneut.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Korrespondent konnte nicht aktualisiert werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Dokumenttyp konnte nicht aktualisiert werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Tag konnte nicht aktualisiert werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Serverinformationen konnten nicht geladen werden.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Serverstatistiken konnten nicht geladen werden.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "UI Einstellungen konnten nicht geladen werden.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Dateiaufgaben konnten nicht geladen werden.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "Der Nutzer konnte nicht gefunden werden.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Ansicht konnte nicht aktualisiert werden, bitte versuche es erneut.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Speicherpfad konnte nicht aktualisiert werden, bitte versuchen Sie es erneut.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Ansicht erfolgreich aktualisiert.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Änderungen verwerfen?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "Die Filterbedingungen der aktiven Ansicht haben sich geändert. Durch Zurücksetzen des aktuellen Filters gehen diese Änderungen verloren. Möchtest du trotzdem fortfahren?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Vom aktuellen Filter erstellen",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Startseite",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Willkommen, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Konfiguriere eine Ansicht so, dass sie auf deiner Startseite angezeigt wird und sie wird hier erscheinen.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistiken",
|
||||||
|
"documentsInInbox": "Dokumente im Posteingang",
|
||||||
|
"totalDocuments": "Dokumente insgesamt",
|
||||||
|
"totalCharacters": "Zeichen insgesamt",
|
||||||
|
"showAll": "Alle anzeigen",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"@startTyping": {},
|
"@startTyping": {},
|
||||||
"doYouReallyWantToDeleteThisView": "Do you really want to delete this view?",
|
"doYouReallyWantToDeleteThisView": "Do you really want to delete this view?",
|
||||||
"@doYouReallyWantToDeleteThisView": {},
|
"@doYouReallyWantToDeleteThisView": {},
|
||||||
"deleteView": "Delete view ",
|
"deleteView": "Delete view {name}?",
|
||||||
"@deleteView": {},
|
"@deleteView": {},
|
||||||
"addedAt": "Added at",
|
"addedAt": "Added at",
|
||||||
"@addedAt": {},
|
"@addedAt": {},
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"@startTyping": {},
|
"@startTyping": {},
|
||||||
"doYouReallyWantToDeleteThisView": "Voulez-vous vraiment supprimer cette vue enregistrée ?",
|
"doYouReallyWantToDeleteThisView": "Voulez-vous vraiment supprimer cette vue enregistrée ?",
|
||||||
"@doYouReallyWantToDeleteThisView": {},
|
"@doYouReallyWantToDeleteThisView": {},
|
||||||
"deleteView": "Supprimer la vue enregistrée ",
|
"deleteView": "Supprimer la vue enregistrée {name}?",
|
||||||
"@deleteView": {},
|
"@deleteView": {},
|
||||||
"addedAt": "Date d’ajout",
|
"addedAt": "Date d’ajout",
|
||||||
"@addedAt": {},
|
"@addedAt": {},
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,5 +876,98 @@
|
|||||||
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
"donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
|
||||||
"@donationDialogContent": {
|
"@donationDialogContent": {
|
||||||
"description": "Text displayed in the donation dialog"
|
"description": "Text displayed in the donation dialog"
|
||||||
|
},
|
||||||
|
"noDocumentsFound": "No documents found.",
|
||||||
|
"@noDocumentsFound": {
|
||||||
|
"description": "Message shown when no documents were found."
|
||||||
|
},
|
||||||
|
"couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
|
||||||
|
"@couldNotDeleteCorrespondent": {
|
||||||
|
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||||
|
},
|
||||||
|
"couldNotDeleteDocumentType": "Could not delete document type, please try again.",
|
||||||
|
"@couldNotDeleteDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteTag": "Could not delete tag, please try again.",
|
||||||
|
"@couldNotDeleteTag": {
|
||||||
|
"description": "Message shown when a tag could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
|
||||||
|
"@couldNotDeleteStoragePath": {
|
||||||
|
"description": "Message shown when a storage path could not be deleted"
|
||||||
|
},
|
||||||
|
"couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
|
||||||
|
"@couldNotUpdateCorrespondent": {
|
||||||
|
"description": "Message shown when a correspondent could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateDocumentType": "Could not update document type, please try again.",
|
||||||
|
"@couldNotUpdateDocumentType": {
|
||||||
|
"description": "Message shown when a document type could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateTag": "Could not update tag, please try again.",
|
||||||
|
"@couldNotUpdateTag": {
|
||||||
|
"description": "Message shown when a tag could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotLoadServerInformation": "Could not load server information.",
|
||||||
|
"@couldNotLoadServerInformation": {
|
||||||
|
"description": "Message shown when the server information could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadStatistics": "Could not load server statistics.",
|
||||||
|
"@couldNotLoadStatistics": {
|
||||||
|
"description": "Message shown when the server statistics could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadUISettings": "Could not load UI settings.",
|
||||||
|
"@couldNotLoadUISettings": {
|
||||||
|
"description": "Message shown when the UI settings could not be loaded"
|
||||||
|
},
|
||||||
|
"couldNotLoadTasks": "Could not load tasks.",
|
||||||
|
"@couldNotLoadTasks": {
|
||||||
|
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
|
||||||
|
},
|
||||||
|
"userNotFound": "User could not be found.",
|
||||||
|
"@userNotFound": {
|
||||||
|
"description": "Message shown when the specified user (e.g. by id) could not be found"
|
||||||
|
},
|
||||||
|
"couldNotUpdateSavedView": "Could not update saved view, please try again.",
|
||||||
|
"@couldNotUpdateSavedView": {
|
||||||
|
"description": "Message shown when a saved view could not be updated"
|
||||||
|
},
|
||||||
|
"couldNotUpdateStoragePath": "Could not update storage path, please try again.",
|
||||||
|
"savedViewSuccessfullyUpdated": "Saved view successfully updated.",
|
||||||
|
"@savedViewSuccessfullyUpdated": {
|
||||||
|
"description": "Message shown when a saved view was successfully updated."
|
||||||
|
},
|
||||||
|
"discardChanges": "Discard changes?",
|
||||||
|
"@discardChanges": {
|
||||||
|
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
|
||||||
|
},
|
||||||
|
"savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
|
||||||
|
"@savedViewChangedDialogContent": {
|
||||||
|
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
|
||||||
|
},
|
||||||
|
"createFromCurrentFilter": "Create from current filter",
|
||||||
|
"@createFromCurrentFilter": {
|
||||||
|
"description": "Tooltip of the \"New saved view\" button"
|
||||||
|
},
|
||||||
|
"home": "Home",
|
||||||
|
"@home": {
|
||||||
|
"description": "Label of the \"Home\" route"
|
||||||
|
},
|
||||||
|
"welcomeUser": "Welcome, {name}!",
|
||||||
|
"@welcomeUser": {
|
||||||
|
"description": "Top message shown on the home page"
|
||||||
|
},
|
||||||
|
"noSavedViewOnHomepageHint": "Configure a saved view to be displayed on your home page and it will show up here.",
|
||||||
|
"@noSavedViewOnHomepageHint": {
|
||||||
|
"description": "Message shown when there is no saved view to display on the home page."
|
||||||
|
},
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"documentsInInbox": "Documents in inbox",
|
||||||
|
"totalDocuments": "Total documents",
|
||||||
|
"totalCharacters": "Total characters",
|
||||||
|
"showAll": "Show all",
|
||||||
|
"@showAll": {
|
||||||
|
"description": "Button label shown on a saved view preview to open this view in the documents page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:device_info_plus/device_info_plus.dart';
|
|||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
@@ -34,7 +35,6 @@ import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
|||||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||||
@@ -109,7 +109,6 @@ void main() async {
|
|||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
iosInfo = await DeviceInfoPlugin().iosInfo;
|
iosInfo = await DeviceInfoPlugin().iosInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
final connectivity = Connectivity();
|
final connectivity = Connectivity();
|
||||||
final localAuthentication = LocalAuthentication();
|
final localAuthentication = LocalAuthentication();
|
||||||
final connectivityStatusService =
|
final connectivityStatusService =
|
||||||
@@ -149,6 +148,7 @@ void main() async {
|
|||||||
final authenticationCubit =
|
final authenticationCubit =
|
||||||
AuthenticationCubit(localAuthService, apiFactory, sessionManager);
|
AuthenticationCubit(localAuthService, apiFactory, sessionManager);
|
||||||
await authenticationCubit.restoreSessionState();
|
await authenticationCubit.restoreSessionState();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -228,11 +228,11 @@ class _GoRouterShellState extends State<GoRouterShell> {
|
|||||||
$loginRoute,
|
$loginRoute,
|
||||||
$verifyIdentityRoute,
|
$verifyIdentityRoute,
|
||||||
$switchingAccountsRoute,
|
$switchingAccountsRoute,
|
||||||
$settingsRoute,
|
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
navigatorKey: rootNavigatorKey,
|
navigatorKey: rootNavigatorKey,
|
||||||
builder: ProviderShellRoute(widget.apiFactory).build,
|
builder: ProviderShellRoute(widget.apiFactory).build,
|
||||||
routes: [
|
routes: [
|
||||||
|
$settingsRoute,
|
||||||
$savedViewsRoute,
|
$savedViewsRoute,
|
||||||
StatefulShellRoute(
|
StatefulShellRoute(
|
||||||
navigatorContainerBuilder: (context, navigationShell, children) {
|
navigatorContainerBuilder: (context, navigationShell, children) {
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ final documentsNavigatorKey = GlobalKey<NavigatorState>();
|
|||||||
final scannerNavigatorKey = GlobalKey<NavigatorState>();
|
final scannerNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final labelsNavigatorKey = GlobalKey<NavigatorState>();
|
final labelsNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final inboxNavigatorKey = GlobalKey<NavigatorState>();
|
final inboxNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final settingsNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
@@ -14,6 +15,7 @@ import 'package:paperless_mobile/features/documents/view/pages/documents_page.da
|
|||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||||
import 'package:paperless_mobile/routes/routes.dart';
|
import 'package:paperless_mobile/routes/routes.dart';
|
||||||
|
import 'package:paperless_mobile/theme.dart';
|
||||||
|
|
||||||
part 'documents_route.g.dart';
|
part 'documents_route.g.dart';
|
||||||
|
|
||||||
@@ -92,14 +94,21 @@ class EditDocumentRoute extends GoRouteData {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, GoRouterState state) {
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
return BlocProvider(
|
final theme = Theme.of(context);
|
||||||
create: (context) => DocumentEditCubit(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
context.read(),
|
value: buildOverlayStyle(
|
||||||
context.read(),
|
theme,
|
||||||
context.read(),
|
systemNavigationBarColor: theme.colorScheme.background,
|
||||||
document: $extra,
|
),
|
||||||
)..loadFieldSuggestions(),
|
child: BlocProvider(
|
||||||
child: const DocumentEditPage(),
|
create: (context) => DocumentEditCubit(
|
||||||
|
context.read(),
|
||||||
|
context.read(),
|
||||||
|
context.read(),
|
||||||
|
document: $extra,
|
||||||
|
)..loadFieldSuggestions(),
|
||||||
|
child: const DocumentEditPage(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ class LandingBranch extends StatefulShellBranchData {
|
|||||||
@TypedGoRoute<LandingRoute>(
|
@TypedGoRoute<LandingRoute>(
|
||||||
path: "/landing",
|
path: "/landing",
|
||||||
name: R.landing,
|
name: R.landing,
|
||||||
routes: [
|
|
||||||
TypedGoRoute<SavedViewRoute>(
|
|
||||||
path: "saved-view",
|
|
||||||
name: R.savedView,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
class LandingRoute extends GoRouteData {
|
class LandingRoute extends GoRouteData {
|
||||||
const LandingRoute();
|
const LandingRoute();
|
||||||
@@ -29,10 +23,3 @@ class LandingRoute extends GoRouteData {
|
|||||||
return const LandingPage();
|
return const LandingPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SavedViewRoute extends GoRouteData {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, GoRouterState state) {
|
|
||||||
return Placeholder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||||
|
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||||
import 'package:paperless_mobile/routes/routes.dart';
|
import 'package:paperless_mobile/routes/routes.dart';
|
||||||
|
import 'package:paperless_mobile/theme.dart';
|
||||||
|
|
||||||
part 'settings_route.g.dart';
|
part 'settings_route.g.dart';
|
||||||
|
|
||||||
@@ -10,8 +13,16 @@ part 'settings_route.g.dart';
|
|||||||
name: R.settings,
|
name: R.settings,
|
||||||
)
|
)
|
||||||
class SettingsRoute extends GoRouteData {
|
class SettingsRoute extends GoRouteData {
|
||||||
|
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, GoRouterState state) {
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
return const SettingsPage();
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
|
value: buildOverlayStyle(
|
||||||
|
Theme.of(context),
|
||||||
|
systemNavigationBarColor: Theme.of(context).colorScheme.background,
|
||||||
|
),
|
||||||
|
child: const SettingsPage(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||||
|
|
||||||
const _classicThemeColorSeed = Colors.lightGreen;
|
const _classicThemeColorSeed = Colors.lightGreen;
|
||||||
@@ -46,6 +47,12 @@ ThemeData buildTheme({
|
|||||||
colorScheme: colorScheme.harmonized(),
|
colorScheme: colorScheme.harmonized(),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
).copyWith(
|
).copyWith(
|
||||||
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||||
|
backgroundColor: colorScheme.surface,
|
||||||
|
),
|
||||||
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
|
backgroundColor: colorScheme.surface,
|
||||||
|
),
|
||||||
cardTheme: _defaultCardTheme,
|
cardTheme: _defaultCardTheme,
|
||||||
inputDecorationTheme: _defaultInputDecorationTheme,
|
inputDecorationTheme: _defaultInputDecorationTheme,
|
||||||
listTileTheme: _defaultListTileTheme,
|
listTileTheme: _defaultListTileTheme,
|
||||||
@@ -60,3 +67,29 @@ ThemeData buildTheme({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemUiOverlayStyle buildOverlayStyle(
|
||||||
|
ThemeData theme, {
|
||||||
|
Color? systemNavigationBarColor,
|
||||||
|
}) {
|
||||||
|
final color = systemNavigationBarColor ??
|
||||||
|
ElevationOverlay.applySurfaceTint(
|
||||||
|
theme.colorScheme.surface,
|
||||||
|
theme.colorScheme.surfaceTint,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return switch (theme.brightness) {
|
||||||
|
Brightness.light => SystemUiOverlayStyle.dark.copyWith(
|
||||||
|
systemNavigationBarColor: color,
|
||||||
|
systemNavigationBarDividerColor: color,
|
||||||
|
// statusBarColor: theme.colorScheme.background,
|
||||||
|
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
Brightness.dark => SystemUiOverlayStyle.light.copyWith(
|
||||||
|
systemNavigationBarColor: color,
|
||||||
|
systemNavigationBarDividerColor: color,
|
||||||
|
// statusBarColor: theme.colorScheme.background,
|
||||||
|
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||||
final result = await getCollection(
|
final result = await getCollection(
|
||||||
"/api/saved_views/",
|
"/api/saved_views/?page_size=100000",
|
||||||
SavedView.fromJson,
|
SavedView.fromJson,
|
||||||
ErrorCode.loadSavedViewsError,
|
ErrorCode.loadSavedViewsError,
|
||||||
client: _client,
|
client: _client,
|
||||||
|
|||||||
@@ -345,6 +345,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.8"
|
||||||
|
defer_pointer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: defer_pointer
|
||||||
|
sha256: d69e6f8c1d0f052d2616cc1db3782e0ea73f42e4c6f6122fd1a548dfe79faf02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.2"
|
||||||
dependency_validator:
|
dependency_validator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 2.3.11+46
|
version: 2.3.12+47
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
@@ -93,6 +93,7 @@ dependencies:
|
|||||||
go_router: ^10.0.0
|
go_router: ^10.0.0
|
||||||
fl_chart: ^0.63.0
|
fl_chart: ^0.63.0
|
||||||
palette_generator: ^0.3.3+2
|
palette_generator: ^0.3.3+2
|
||||||
|
defer_pointer: ^0.0.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
|
|||||||
Reference in New Issue
Block a user