mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 12:08:05 -06:00
feat: Rework error handling, upgrade dio, fixed bugs
- Fix grey screen bug when adding labels from documnet upload - Add more permission checks to conditionally show widgets
This commit is contained in:
@@ -70,13 +70,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
Future<void> loadFullContent() async {
|
||||
final doc = await _api.find(state.document.id);
|
||||
if (doc == null) {
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(
|
||||
isFullContentLoaded: true,
|
||||
fullContent: doc.content,
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
isFullContentLoaded: true,
|
||||
fullContent: doc.content,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> assignAsn(
|
||||
@@ -99,13 +98,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
Future<ResultType> openDocumentInSystemViewer() async {
|
||||
final cacheDir = await FileService.temporaryDirectory;
|
||||
//TODO: Why is this cleared here?
|
||||
await FileService.clearDirectoryContent(PaperlessDirectoryType.temporary);
|
||||
if (state.metaData == null) {
|
||||
await loadMetaData();
|
||||
}
|
||||
final desc = FileDescription.fromPath(
|
||||
state.metaData!.mediaFilename.replaceAll("/", " "));
|
||||
state.metaData!.mediaFilename.replaceAll("/", " "),
|
||||
);
|
||||
|
||||
final fileName = "${desc.filename}.pdf";
|
||||
final file = File("${cacheDir.path}/$fileName");
|
||||
|
||||
@@ -287,8 +287,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Widget _buildEditButton() {
|
||||
bool canEdit = context.watchInternetConnection &&
|
||||
LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
if (!canEdit) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -319,8 +318,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
final isConnected = connectivityState.isConnected;
|
||||
|
||||
final canDelete = isConnected &&
|
||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.delete, PermissionTarget.document);
|
||||
LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
@@ -430,7 +428,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
try {
|
||||
await context.read<DocumentDetailsCubit>().delete(document);
|
||||
showSnackBar(context, S.of(context)!.documentSuccessfullyDeleted);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} finally {
|
||||
// Document deleted => go back to primary route
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.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/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -48,10 +47,7 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userCanEditDocument =
|
||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.change,
|
||||
PermissionTarget.document,
|
||||
);
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.document.archiveSerialNumber !=
|
||||
@@ -124,12 +120,14 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
.read<DocumentDetailsCubit>()
|
||||
.assignAsn(widget.document, asn: asn)
|
||||
.then((value) => _onAsnUpdated())
|
||||
.onError<PaperlessServerException>(
|
||||
.onError<PaperlessApiException>(
|
||||
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
||||
)
|
||||
.onError<PaperlessValidationErrors>(
|
||||
(error, stackTrace) => setState(() => _errors = error),
|
||||
);
|
||||
.onError<PaperlessFormValidationException>(
|
||||
(error, stackTrace) {
|
||||
setState(() => _errors = error.validationMessages);
|
||||
},
|
||||
);
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@@ -141,9 +139,10 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
autoAssign: true,
|
||||
)
|
||||
.then((value) => _onAsnUpdated())
|
||||
.onError<PaperlessServerException>(
|
||||
.onError<PaperlessApiException>(
|
||||
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
||||
);
|
||||
)
|
||||
.catchError((error) => showGenericError(context, error));
|
||||
}
|
||||
|
||||
void _onAsnUpdated() {
|
||||
|
||||
@@ -95,7 +95,7 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
||||
locale: globalSettings.preferredLocaleSubtag,
|
||||
);
|
||||
// showSnackBar(context, S.of(context)!.documentSuccessfullyDownloaded);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
@@ -45,38 +46,35 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
context: context,
|
||||
label: S.of(context)!.createdAt,
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
Visibility(
|
||||
visible: document.documentType != null,
|
||||
child: DetailsItem(
|
||||
if (document.documentType != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableDocumentTypes[document.documentType],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
),
|
||||
Visibility(
|
||||
visible: document.correspondent != null,
|
||||
child: DetailsItem(
|
||||
if (document.correspondent != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewCorrespondents)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableCorrespondents[document.correspondent],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
),
|
||||
Visibility(
|
||||
visible: document.storagePath != null,
|
||||
child: DetailsItem(
|
||||
if (document.storagePath != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
label: availableStoragePaths[document.storagePath],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
),
|
||||
Visibility(
|
||||
visible: document.tags.isNotEmpty,
|
||||
child: DetailsItem(
|
||||
if (document.tags.isNotEmpty &&
|
||||
LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.tags,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
@@ -86,7 +84,6 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -90,7 +90,7 @@ class _DocumentShareButtonState extends State<DocumentShareButton> {
|
||||
await context.read<DocumentDetailsCubit>().shareDocument(
|
||||
shareOriginal: original,
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
|
||||
@@ -123,12 +123,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.correspondent,
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateCorrespondents,
|
||||
),
|
||||
if (_filteredSuggestions
|
||||
?.hasSuggestedCorrespondents ??
|
||||
@@ -164,12 +160,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.documentType,
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateDocumentTypes,
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue:
|
||||
@@ -214,12 +206,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
child: AddStoragePathPage(
|
||||
initalName: initialValue),
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.storagePath,
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options: state.storagePaths,
|
||||
@@ -328,7 +316,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
try {
|
||||
await context.read<DocumentEditCubit>().updateDocument(mergedDocument);
|
||||
showSnackBar(context, S.of(context)!.documentSuccessfullyUpdated);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} finally {
|
||||
setState(() {
|
||||
|
||||
@@ -22,7 +22,7 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
scans.removeAt(fileIndex);
|
||||
emit(scans);
|
||||
} catch (_) {
|
||||
throw const PaperlessServerException(ErrorCode.scanRemoveFailed);
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
imageCache.clear();
|
||||
emit([]);
|
||||
} catch (_) {
|
||||
throw const PaperlessServerException(ErrorCode.scanRemoveFailed);
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,9 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: const SliverSearchBar(),
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.scanner,
|
||||
),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: actionsHandle,
|
||||
@@ -322,7 +324,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
onDelete: () async {
|
||||
try {
|
||||
context.read<DocumentScannerCubit>().removeScan(index);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
@@ -339,7 +341,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
void _reset(BuildContext context) {
|
||||
try {
|
||||
context.read<DocumentScannerCubit>().reset();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -360,7 +362,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
)) {
|
||||
showErrorMessage(
|
||||
context,
|
||||
const PaperlessServerException(ErrorCode.unsupportedFileFormat),
|
||||
const PaperlessApiException(ErrorCode.unsupportedFileFormat),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SliverSearchBar extends StatelessWidget {
|
||||
final bool floating;
|
||||
final bool pinned;
|
||||
final String titleText;
|
||||
const SliverSearchBar({
|
||||
super.key,
|
||||
this.floating = false,
|
||||
this.pinned = false,
|
||||
required this.titleText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
sliver: SliverPersistentHeader(
|
||||
floating: floating,
|
||||
pinned: pinned,
|
||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||
minExtent: kToolbarHeight,
|
||||
maxExtent: kToolbarHeight,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: const DocumentSearchBar(),
|
||||
),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewDocuments) {
|
||||
return SliverAppBar(
|
||||
toolbarHeight: kToolbarHeight,
|
||||
flexibleSpace: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: const DocumentSearchBar(),
|
||||
),
|
||||
),
|
||||
);
|
||||
automaticallyImplyLeading: false,
|
||||
);
|
||||
} else {
|
||||
return SliverAppBar(
|
||||
title: Text(titleText),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(6),
|
||||
icon: GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.listenable(),
|
||||
builder: (context, box, _) {
|
||||
final account = box.get(settings.currentLoggedInUser!)!;
|
||||
return UserAvatar(account: account);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
final apiVersion = context.read<ApiVersion>();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => Provider.value(
|
||||
value: apiVersion,
|
||||
child: const ManageAccountsPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,17 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.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/generated/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DocumentUploadResult {
|
||||
final bool success;
|
||||
@@ -56,7 +57,7 @@ class _DocumentUploadPreparationPageState
|
||||
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
|
||||
PaperlessValidationErrors _errors = {};
|
||||
Map<String, String> _errors = {};
|
||||
bool _isUploadLoading = false;
|
||||
late bool _syncTitleAndFilename;
|
||||
bool _showDatePickerDeleteIcon = false;
|
||||
@@ -197,54 +198,64 @@ class _DocumentUploadPreparationPageState
|
||||
),
|
||||
),
|
||||
// Correspondent
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddCorrespondentPage(initialName: initialName),
|
||||
if (LocalUserAccount
|
||||
.current.paperlessUser.canViewCorrespondents)
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
child: AddCorrespondentPage(initialName: initialName),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: state.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser.canCreateCorrespondents,
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: state.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.correspondent,
|
||||
),
|
||||
),
|
||||
// Document type
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddDocumentTypePage(initialName: initialName),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
),
|
||||
Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
)
|
||||
],
|
||||
child: AddDocumentTypePage(initialName: initialName),
|
||||
),
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
options: state.documentTypes,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser.canCreateDocumentTypes,
|
||||
),
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
options: state.documentTypes,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.documentType,
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: state.tags,
|
||||
),
|
||||
),
|
||||
TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: state.tags,
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context)!.uploadInferValuesHint,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
@@ -301,14 +312,14 @@ class _DocumentUploadPreparationPageState
|
||||
context,
|
||||
DocumentUploadResult(true, taskId),
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessValidationErrors catch (errors) {
|
||||
setState(() => _errors = errors);
|
||||
} on PaperlessFormValidationException catch (exception) {
|
||||
setState(() => _errors = exception.validationMessages);
|
||||
} catch (unknownError, stackTrace) {
|
||||
debugPrint(unknownError.toString());
|
||||
showErrorMessage(
|
||||
context, const PaperlessServerException.unknown(), stackTrace);
|
||||
context, const PaperlessApiException.unknown(), stackTrace);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isUploadLoading = false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.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/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
||||
@@ -53,14 +54,16 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final showSavedViews =
|
||||
LocalUserAccount.current.paperlessUser.canViewSavedViews;
|
||||
_tabController = TabController(
|
||||
length: 2,
|
||||
length: showSavedViews ? 2 : 1,
|
||||
vsync: this,
|
||||
);
|
||||
Future.wait([
|
||||
context.read<DocumentsCubit>().reload(),
|
||||
context.read<SavedViewCubit>().reload(),
|
||||
]).onError<PaperlessServerException>(
|
||||
]).onError<PaperlessApiException>(
|
||||
(error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
return [];
|
||||
@@ -105,7 +108,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
listener: (context, state) {
|
||||
try {
|
||||
context.read<DocumentsCubit>().reload();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
@@ -197,7 +200,10 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isEmpty) {
|
||||
return const SliverSearchBar(floating: true);
|
||||
return SliverSearchBar(
|
||||
floating: true,
|
||||
titleText: S.of(context)!.documents,
|
||||
);
|
||||
} else {
|
||||
return DocumentSelectionSliverAppBar(
|
||||
state: state,
|
||||
@@ -226,7 +232,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.documents),
|
||||
Tab(text: S.of(context)!.views),
|
||||
if (LocalUserAccount.current.paperlessUser
|
||||
.canViewSavedViews)
|
||||
Tab(text: S.of(context)!.views),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -268,14 +276,16 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildSavedViewsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (LocalUserAccount
|
||||
.current.paperlessUser.canViewSavedViews)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildSavedViewsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -334,7 +344,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
context
|
||||
.read<DocumentsCubit>()
|
||||
.loadMore()
|
||||
.onError<PaperlessServerException>(
|
||||
.onError<PaperlessApiException>(
|
||||
(error, stackTrace) => showErrorMessage(
|
||||
context,
|
||||
error,
|
||||
@@ -419,7 +429,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
if (newView != null) {
|
||||
try {
|
||||
await context.read<SavedViewCubit>().add(newView);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -472,7 +482,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
.read<DocumentsCubit>()
|
||||
.updateFilter(filter: filterIntent.filter!);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -524,7 +534,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -555,7 +565,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -586,7 +596,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -617,7 +627,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -626,7 +636,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
try {
|
||||
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
||||
await context.read<DocumentsCubit>().reload();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
@@ -635,7 +645,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
try {
|
||||
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
||||
await context.read<SavedViewCubit>().reload();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,10 +160,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.documentType,
|
||||
),
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateDocumentTypes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,10 +173,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.correspondent,
|
||||
),
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateCorrespondents,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,10 +186,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.storagePath,
|
||||
),
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateStoragePaths,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
||||
S.of(context)!.documentsSuccessfullyDeleted,
|
||||
);
|
||||
context.read<DocumentsCubit>().resetSelection();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
onDelete(context, label);
|
||||
} on PaperlessServerException catch (error) {
|
||||
} on PaperlessApiException catch (error) {
|
||||
showErrorMessage(context, error);
|
||||
} catch (error, stackTrace) {
|
||||
log("An error occurred!", error: error, stackTrace: stackTrace);
|
||||
|
||||
@@ -24,10 +24,8 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.delete,
|
||||
PermissionTarget.correspondent,
|
||||
),
|
||||
canDelete:
|
||||
LocalUserAccount.current.paperlessUser.canDeleteCorrespondents,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -22,10 +22,8 @@ class EditDocumentTypePage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.delete,
|
||||
PermissionTarget.documentType,
|
||||
),
|
||||
canDelete:
|
||||
LocalUserAccount.current.paperlessUser.canDeleteDocumentTypes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.delete,
|
||||
PermissionTarget.storagePath,
|
||||
),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteStoragePaths,
|
||||
additionalFields: [
|
||||
StoragePathAutofillFormBuilderField(
|
||||
name: StoragePath.pathKey,
|
||||
|
||||
@@ -26,10 +26,7 @@ class EditTagPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceTag(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeTag(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.delete,
|
||||
PermissionTarget.tag,
|
||||
),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteTags,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
initialValue: tag.color,
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/type/types.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';
|
||||
@@ -54,7 +53,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
|
||||
late bool _enableMatchFormField;
|
||||
|
||||
PaperlessValidationErrors _errors = {};
|
||||
Map<String, String> _errors = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -69,7 +68,8 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
Widget build(BuildContext context) {
|
||||
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
||||
getSelectableMatchingAlgorithmValues(
|
||||
context.watch<ApiVersion>().hasMultiUserSupport);
|
||||
context.watch<ApiVersion>().hasMultiUserSupport,
|
||||
);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
@@ -168,10 +168,10 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
final parsed = widget.fromJsonT(mergedJson);
|
||||
final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
|
||||
Navigator.pop(context, createdLabel);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessValidationErrors catch (errors) {
|
||||
setState(() => _errors = errors);
|
||||
} on PaperlessFormValidationException catch (exception) {
|
||||
setState(() => _errors = exception.validationMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
int _currentIndex = 0;
|
||||
late Timer _inboxTimer;
|
||||
Timer? _inboxTimer;
|
||||
late final StreamSubscription _shareMediaSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_listenToInboxChanges();
|
||||
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser!;
|
||||
@@ -73,13 +73,15 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
void _listenToInboxChanges() {
|
||||
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
} else {
|
||||
context.read<InboxCubit>().refreshItemsInInboxCount();
|
||||
}
|
||||
});
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags) {
|
||||
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
} else {
|
||||
context.read<InboxCubit>().refreshItemsInInboxCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -89,7 +91,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
log('App is now in foreground');
|
||||
context.read<ConnectivityCubit>().reload();
|
||||
log("Reloaded device connectivity state");
|
||||
if (!_inboxTimer.isActive) {
|
||||
if (!(_inboxTimer?.isActive ?? true)) {
|
||||
_listenToInboxChanges();
|
||||
}
|
||||
break;
|
||||
@@ -98,7 +100,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
case AppLifecycleState.detached:
|
||||
default:
|
||||
log('App is now in background');
|
||||
_inboxTimer.cancel();
|
||||
_inboxTimer?.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +108,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_inboxTimer.cancel();
|
||||
_inboxTimer?.cancel();
|
||||
_shareMediaSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -158,8 +160,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add, PermissionTarget.document)) {
|
||||
if (!LocalUserAccount.current.paperlessUser.canCreateDocuments) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "You do not have the permissions to upload documents.",
|
||||
);
|
||||
@@ -200,8 +201,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
),
|
||||
label: S.of(context)!.documents,
|
||||
),
|
||||
if (LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add, PermissionTarget.document))
|
||||
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.document_scanner_outlined),
|
||||
selectedIcon: Icon(
|
||||
@@ -218,33 +218,31 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
),
|
||||
label: S.of(context)!.labels,
|
||||
),
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.inbox_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.inbox,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.inbox_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.inbox,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: S.of(context)!.inbox,
|
||||
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Badge.count(
|
||||
isLabelVisible: state.itemsInInboxCount > 0,
|
||||
count: state.itemsInInboxCount,
|
||||
child: icon,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
label: S.of(context)!.inbox,
|
||||
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Badge.count(
|
||||
isLabelVisible: state.itemsInInboxCount > 0,
|
||||
count: state.itemsInInboxCount,
|
||||
child: icon,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
final routes = <Widget>[
|
||||
const DocumentsPage(),
|
||||
if (LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.document,
|
||||
))
|
||||
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||
const ScannerPage(),
|
||||
const LabelsPage(),
|
||||
const InboxPage(),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags) const InboxPage(),
|
||||
];
|
||||
return MultiBlocListener(
|
||||
listeners: [
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
@@ -43,7 +44,14 @@ class HomeRoute extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
final currentLocalUserId = settings.currentLoggedInUser!;
|
||||
final currentLocalUserId = settings.currentLoggedInUser;
|
||||
if (currentLocalUserId == null) {
|
||||
// This is the case when the current user logs out of the app.
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
final currentUser =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.get(currentLocalUserId)!;
|
||||
final apiVersion = ApiVersion(paperlessApiVersion);
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
@@ -104,12 +112,31 @@ class HomeRoute extends StatelessWidget {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
||||
update: (context, value, previous) =>
|
||||
LabelRepository(value)..initialize(),
|
||||
update: (context, value, previous) {
|
||||
final repo = LabelRepository(value);
|
||||
if (currentUser.paperlessUser.canViewCorrespondents) {
|
||||
repo.findAllCorrespondents();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewDocumentTypes) {
|
||||
repo.findAllDocumentTypes();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewTags) {
|
||||
repo.findAllTags();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewStoragePaths) {
|
||||
repo.findAllStoragePaths();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
||||
update: (context, value, previous) =>
|
||||
SavedViewRepository(value)..initialize(),
|
||||
update: (context, value, previous) {
|
||||
final repo = SavedViewRepository(value);
|
||||
if (currentUser.paperlessUser.canViewSavedViews) {
|
||||
repo.initialize();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
],
|
||||
builder: (context, child) {
|
||||
|
||||
@@ -38,8 +38,8 @@ class _InboxPageState extends State<InboxPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canEditDocument = LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
||||
final canEditDocument =
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
@@ -65,7 +65,9 @@ class _InboxPageState extends State<InboxPage>
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: const SliverSearchBar(),
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.inbox,
|
||||
),
|
||||
)
|
||||
],
|
||||
body: BlocBuilder<InboxCubit, InboxState>(
|
||||
@@ -222,14 +224,14 @@ class _InboxPageState extends State<InboxPage>
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on ServerMessageException catch (error) {
|
||||
showGenericError(context, error.message);
|
||||
} catch (error) {
|
||||
showErrorMessage(
|
||||
context,
|
||||
const PaperlessServerException.unknown(),
|
||||
const PaperlessApiException.unknown(),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
@@ -243,7 +245,7 @@ class _InboxPageState extends State<InboxPage>
|
||||
await context
|
||||
.read<InboxCubit>()
|
||||
.undoRemoveFromInbox(document, removedTags);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,10 +238,8 @@ class _InboxItemState extends State<InboxItem> {
|
||||
}
|
||||
|
||||
Widget _buildActions(BuildContext context) {
|
||||
final canEdit = LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
||||
final canDelete = LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.delete, PermissionTarget.document);
|
||||
final canEdit = LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
final canDelete = LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||
final chipShape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
);
|
||||
|
||||
@@ -73,10 +73,7 @@ class TagsFormField extends StatelessWidget {
|
||||
initialValue: field.value,
|
||||
allowOnlySelection: allowOnlySelection,
|
||||
allowCreation: allowCreation &&
|
||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.add,
|
||||
PermissionTarget.tag,
|
||||
),
|
||||
LocalUserAccount.current.paperlessUser.canCreateTags,
|
||||
allowExclude: allowExclude,
|
||||
),
|
||||
onClosed: (data) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
@@ -39,291 +42,327 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
late final TabController _tabController;
|
||||
int _currentIndex = 0;
|
||||
|
||||
int _calculateTabCount(UserModel user) => [
|
||||
user.canViewCorrespondents,
|
||||
user.canViewDocumentTypes,
|
||||
user.canViewTags,
|
||||
user.canViewStoragePaths,
|
||||
].fold(0, (value, element) => value + (element ? 1 : 0));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_tabController = TabController(length: 4, vsync: this)
|
||||
final user = LocalUserAccount.current.paperlessUser;
|
||||
_tabController = TabController(
|
||||
length: _calculateTabCount(user), vsync: this)
|
||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: [
|
||||
_openAddCorrespondentPage,
|
||||
_openAddDocumentTypePage,
|
||||
_openAddTagPage,
|
||||
_openAddStoragePathPage,
|
||||
][_currentIndex],
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: const SliverSearchBar(),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
||||
builder: (context, box, child) {
|
||||
final currentUserId =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser;
|
||||
final user = box.get(currentUserId)!.paperlessUser;
|
||||
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: [
|
||||
if (user.canViewCorrespondents) _openAddCorrespondentPage,
|
||||
if (user.canViewDocumentTypes) _openAddDocumentTypePage,
|
||||
if (user.canViewTags) _openAddTagPage,
|
||||
if (user.canViewStoragePaths) _openAddStoragePathPage,
|
||||
][_currentIndex],
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: tabBarHandle,
|
||||
sliver: SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||
child: ColoredTabBar(
|
||||
tabBar: TabBar(
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.labels,
|
||||
),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: tabBarHandle,
|
||||
sliver: SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||
child: ColoredTabBar(
|
||||
tabBar: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
if (user.canViewCorrespondents)
|
||||
Tab(
|
||||
icon: Tooltip(
|
||||
message: S.of(context)!.correspondents,
|
||||
child: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (user.canViewDocumentTypes)
|
||||
Tab(
|
||||
icon: Tooltip(
|
||||
message: S.of(context)!.documentTypes,
|
||||
child: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (user.canViewTags)
|
||||
Tab(
|
||||
icon: Tooltip(
|
||||
message: S.of(context)!.tags,
|
||||
child: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (user.canViewStoragePaths)
|
||||
Tab(
|
||||
icon: Tooltip(
|
||||
message: S.of(context)!.storagePaths,
|
||||
child: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
minExtent: kTextTabBarHeight,
|
||||
maxExtent: kTextTabBarHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<LabelCubit, LabelState>(
|
||||
builder: (context, state) {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final metrics = notification.metrics;
|
||||
if (metrics.maxScrollExtent == 0) {
|
||||
return true;
|
||||
}
|
||||
final desiredTab =
|
||||
((metrics.pixels / metrics.maxScrollExtent) *
|
||||
(_tabController.length - 1))
|
||||
.round();
|
||||
|
||||
if (metrics.axis == Axis.horizontal &&
|
||||
_currentIndex != desiredTab) {
|
||||
setState(() => _currentIndex = desiredTab);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: RefreshIndicator(
|
||||
edgeOffset: kTextTabBarHeight,
|
||||
notificationPredicate: (notification) =>
|
||||
connectedState.isConnected,
|
||||
onRefresh: () async {
|
||||
try {
|
||||
await [
|
||||
context
|
||||
.read<LabelCubit>()
|
||||
.reloadCorrespondents,
|
||||
context
|
||||
.read<LabelCubit>()
|
||||
.reloadDocumentTypes,
|
||||
context.read<LabelCubit>().reloadTags,
|
||||
context.read<LabelCubit>().reloadStoragePaths,
|
||||
][_currentIndex]
|
||||
.call();
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint(
|
||||
"[LabelsPage] RefreshIndicator.onRefresh "
|
||||
"${[
|
||||
"correspondents",
|
||||
"document types",
|
||||
"tags",
|
||||
"storage paths"
|
||||
][_currentIndex]}: "
|
||||
"An error occurred (${error.toString()})",
|
||||
);
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
}
|
||||
},
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
children: [
|
||||
if (user.canViewCorrespondents)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<Correspondent>(
|
||||
labels: state.correspondents,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
correspondent:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditCorrespondents,
|
||||
canAddNew:
|
||||
user.canCreateCorrespondents,
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewCorrespondent,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noCorrespondentsSetUp,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
if (user.canViewDocumentTypes)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<DocumentType>(
|
||||
labels: state.documentTypes,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
documentType:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditDocumentTypes,
|
||||
canAddNew:
|
||||
user.canCreateDocumentTypes,
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewDocumentType,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noDocumentTypesSetUp,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
if (user.canViewTags)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<Tag>(
|
||||
labels: state.tags,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
tags: TagsQuery.ids(
|
||||
include: [label.id!]),
|
||||
),
|
||||
canEdit: user.canEditTags,
|
||||
canAddNew: user.canCreateTags,
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewTag,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noTagsSetUp,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
if (user.canViewStoragePaths)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<StoragePath>(
|
||||
labels: state.storagePaths,
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
storagePath:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditStoragePaths,
|
||||
canAddNew:
|
||||
user.canCreateStoragePaths,
|
||||
contentBuilder: (path) =>
|
||||
Text(path.path),
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewStoragePath,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noStoragePathsSetUp,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
minExtent: kTextTabBarHeight,
|
||||
maxExtent: kTextTabBarHeight),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<LabelCubit, LabelState>(
|
||||
builder: (context, state) {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final metrics = notification.metrics;
|
||||
if (metrics.maxScrollExtent == 0) {
|
||||
return true;
|
||||
}
|
||||
final desiredTab =
|
||||
((metrics.pixels / metrics.maxScrollExtent) *
|
||||
(_tabController.length - 1))
|
||||
.round();
|
||||
|
||||
if (metrics.axis == Axis.horizontal &&
|
||||
_currentIndex != desiredTab) {
|
||||
setState(() => _currentIndex = desiredTab);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: RefreshIndicator(
|
||||
edgeOffset: kTextTabBarHeight,
|
||||
notificationPredicate: (notification) =>
|
||||
connectedState.isConnected,
|
||||
onRefresh: () async {
|
||||
try {
|
||||
await [
|
||||
context.read<LabelCubit>().reloadCorrespondents,
|
||||
context.read<LabelCubit>().reloadDocumentTypes,
|
||||
context.read<LabelCubit>().reloadTags,
|
||||
context.read<LabelCubit>().reloadStoragePaths,
|
||||
][_currentIndex]
|
||||
.call();
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint(
|
||||
"[LabelsPage] RefreshIndicator.onRefresh "
|
||||
"${[
|
||||
"correspondents",
|
||||
"document types",
|
||||
"tags",
|
||||
"storage paths"
|
||||
][_currentIndex]}: "
|
||||
"An error occurred (${error.toString()})",
|
||||
);
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
}
|
||||
},
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<Correspondent>(
|
||||
labels: state.correspondents,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent:
|
||||
IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.change,
|
||||
PermissionTarget.correspondent),
|
||||
canAddNew: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add,
|
||||
PermissionTarget.correspondent),
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewCorrespondent,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noCorrespondentsSetUp,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<DocumentType>(
|
||||
labels: state.documentTypes,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType:
|
||||
IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.change,
|
||||
PermissionTarget.documentType),
|
||||
canAddNew: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add,
|
||||
PermissionTarget.documentType),
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewDocumentType,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noDocumentTypesSetUp,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<Tag>(
|
||||
labels: state.tags,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags:
|
||||
TagsQuery.ids(include: [label.id!]),
|
||||
),
|
||||
canEdit: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.change,
|
||||
PermissionTarget.tag),
|
||||
canAddNew: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add,
|
||||
PermissionTarget.tag),
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewTag,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noTagsSetUp,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<StoragePath>(
|
||||
labels: state.storagePaths,
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath:
|
||||
IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(
|
||||
PermissionAction.change,
|
||||
PermissionTarget.storagePath),
|
||||
canAddNew: LocalUserAccount
|
||||
.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add,
|
||||
PermissionTarget.storagePath),
|
||||
contentBuilder: (path) => Text(path.path),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewStoragePath,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noStoragePathsSetUp,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _openEditCorrespondentPage(Correspondent correspondent) {
|
||||
|
||||
@@ -36,8 +36,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
|
||||
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
||||
final canOpen = (label.documentCount ?? 0) > 0 &&
|
||||
LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.view, PermissionTarget.document);
|
||||
LocalUserAccount.current.paperlessUser.canViewDocuments;
|
||||
return TextButton.icon(
|
||||
label: const Icon(Icons.link),
|
||||
icon: Text(formatMaxCount(label.documentCount)),
|
||||
|
||||
@@ -365,7 +365,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
apiVersion: apiVersion,
|
||||
)
|
||||
.findCurrentUser();
|
||||
} on DioError catch (error, stackTrace) {
|
||||
} on DioException catch (error, stackTrace) {
|
||||
_debugPrintMessage(
|
||||
"_addUser",
|
||||
"An error occurred: ${error.message}",
|
||||
|
||||
@@ -105,7 +105,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
ServerConnectionPage(
|
||||
titleString: widget.titleString,
|
||||
titleText: widget.titleString,
|
||||
formBuilderKey: _formKey,
|
||||
onContinue: () {
|
||||
_pageController.nextPage(
|
||||
@@ -126,7 +126,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
Future<void> _login() async {
|
||||
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final form = _formKey.currentState!.value;
|
||||
@@ -150,7 +149,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
form[ServerAddressFormField.fkServerAddress],
|
||||
clientCert,
|
||||
);
|
||||
} on PaperlessServerException catch (error) {
|
||||
} on PaperlessApiException catch (error) {
|
||||
showErrorMessage(context, error);
|
||||
} on ServerMessageException catch (error) {
|
||||
showLocalizedError(context, error.message);
|
||||
|
||||
@@ -66,7 +66,10 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
.values
|
||||
.where((element) => element.contains(textEditingValue.text));
|
||||
},
|
||||
onSelected: (option) => _formatInput(),
|
||||
onSelected: (option) {
|
||||
_formatInput();
|
||||
field.didChange(_textEditingController.text);
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||
return TextField(
|
||||
@@ -111,6 +114,10 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
String address = _textEditingController.text.trim();
|
||||
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||
_textEditingController.text = address;
|
||||
_textEditingController.selection = TextSelection(
|
||||
baseOffset: address.length,
|
||||
extentOffset: address.length,
|
||||
);
|
||||
widget.onSubmit(address);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ import 'package:provider/provider.dart';
|
||||
|
||||
class ServerConnectionPage extends StatefulWidget {
|
||||
final GlobalKey<FormBuilderState> formBuilderKey;
|
||||
final void Function() onContinue;
|
||||
final String titleString;
|
||||
final VoidCallback onContinue;
|
||||
final String titleText;
|
||||
|
||||
const ServerConnectionPage({
|
||||
super.key,
|
||||
required this.formBuilderKey,
|
||||
required this.onContinue,
|
||||
required this.titleString,
|
||||
required this.titleText,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -36,7 +36,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: kToolbarHeight - 4,
|
||||
title: Text(widget.titleString),
|
||||
title: Text(widget.titleText),
|
||||
bottom: PreferredSize(
|
||||
child: _isCheckingConnection
|
||||
? const LinearProgressIndicator()
|
||||
|
||||
@@ -26,7 +26,7 @@ mixin DocumentPagingViewMixin<T extends StatefulWidget,
|
||||
if (shouldLoadMoreDocuments) {
|
||||
try {
|
||||
await _bloc.loadMore();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
||||
super.initState();
|
||||
try {
|
||||
context.read<SimilarDocumentsCubit>().initialize();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user