diff --git a/crowdin.yml b/crowdin.yml index d086c08..51718af 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,4 @@ +project_id: "568557" files: [ { "source" : "/lib/l10n/intl_en.arb", diff --git a/lib/features/document_details/view/pages/document_details_page.dart b/lib/features/document_details/view/pages/document_details_page.dart index 4f4249e..97b8c7b 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -341,13 +341,6 @@ class _DocumentDetailsPageState extends State { document: state.document, enabled: isConnected, ), - // //TODO: Enable again, need new pdf viewer package... - // IconButton( - // tooltip: S.of(context)!.previewTooltip, - // icon: const Icon(Icons.visibility), - // onPressed: - // (isConnected) ? () => _onOpen(state.document) : null, - // ).paddedOnly(right: 4.0), IconButton( tooltip: S.of(context)!.openInSystemViewer, icon: const Icon(Icons.open_in_new), @@ -355,7 +348,7 @@ class _DocumentDetailsPageState extends State { ).paddedOnly(right: 4.0), DocumentShareButton(document: state.document), IconButton( - tooltip: S.of(context)!.print, //TODO: INTL + tooltip: S.of(context)!.print, onPressed: () => context.read().printDocument(), icon: const Icon(Icons.print), diff --git a/lib/features/home/view/scaffold_with_navigation_bar.dart b/lib/features/home/view/scaffold_with_navigation_bar.dart index 0d67599..fab2344 100644 --- a/lib/features/home/view/scaffold_with_navigation_bar.dart +++ b/lib/features/home/view/scaffold_with_navigation_bar.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -46,24 +45,21 @@ class ScaffoldWithNavigationBarState extends State { if (widget.authenticatedUser.canViewDocuments) { widget.navigationShell.goBranch(index); } else { - showSnackBar(context, - "You do not have the required permissions to access this page."); + showSnackBar(context, S.of(context)!.missingPermissions); } break; case _scannerIndex: if (widget.authenticatedUser.canCreateDocuments) { widget.navigationShell.goBranch(index); } else { - showSnackBar(context, - "You do not have the required permissions to access this page."); + showSnackBar(context, S.of(context)!.missingPermissions); } break; case _labelsIndex: if (widget.authenticatedUser.canViewAnyLabel) { widget.navigationShell.goBranch(index); } else { - showSnackBar(context, - "You do not have the required permissions to access this page."); + showSnackBar(context, S.of(context)!.missingPermissions); } break; case _inboxIndex: @@ -71,8 +67,7 @@ class ScaffoldWithNavigationBarState extends State { widget.authenticatedUser.canViewTags) { widget.navigationShell.goBranch(index); } else { - showSnackBar(context, - "You do not have the required permissions to access this page."); + showSnackBar(context, S.of(context)!.missingPermissions); } break; default: diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index ffee09c..3356fd5 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -61,7 +61,6 @@ class InboxCubit extends HydratedCubit Future initialize() async { await refreshItemsInInboxCount(false); await loadInbox(); - super.initialize(); } Future refreshItemsInInboxCount([bool shouldLoadInbox = true]) async { diff --git a/lib/features/inbox/view/pages/inbox_page.dart b/lib/features/inbox/view/pages/inbox_page.dart index fecaf54..c1638f2 100644 --- a/lib/features/inbox/view/pages/inbox_page.dart +++ b/lib/features/inbox/view/pages/inbox_page.dart @@ -214,6 +214,10 @@ class _InboxPageState extends State } Future _onItemDismissed(DocumentModel doc) async { + if (!context.read().paperlessUser.canEditDocuments) { + showSnackBar(context, S.of(context)!.missingPermissions); + return false; + } try { final removedTags = await context.read().removeFromInbox(doc); showSnackBar( diff --git a/lib/features/landing/view/landing_page.dart b/lib/features/landing/view/landing_page.dart index ecca02b..01a7027 100644 --- a/lib/features/landing/view/landing_page.dart +++ b/lib/features/landing/view/landing_page.dart @@ -1,7 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/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/document_search/view/sliver_search_bar.dart'; +import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart'; +import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; +import 'package:paperless_mobile/routes/routes.dart'; +import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; +import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart'; +import 'package:fl_chart/fl_chart.dart'; class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -14,6 +25,7 @@ class _LandingPageState extends State { final _searchBarHandle = SliverOverlapAbsorberHandle(); @override Widget build(BuildContext context) { + final currentUser = context.watch().paperlessUser; return SafeArea( child: Scaffold( drawer: const AppDrawer(), @@ -29,19 +41,126 @@ class _LandingPageState extends State { ], body: CustomScrollView( slivers: [ - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverToBoxAdapter( - child: Text( - "Welcome!", - style: Theme.of(context).textTheme.titleLarge, - ), - ), + SliverToBoxAdapter( + child: Text( + "Welcome to Paperless Mobile, ${currentUser.fullName ?? currentUser.username}!", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .displaySmall + ?.copyWith(fontSize: 28), + ).padded(24), ), + SliverToBoxAdapter(child: _buildStatisticsCard(context)), ], ), ), ), ); } + + Widget _buildStatisticsCard(BuildContext context) { + return FutureBuilder( + future: context.read().getServerStatistics(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Card( + margin: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Statistics", //TODO: INTL + style: Theme.of(context).textTheme.titleLarge, + ), + const Padding( + padding: EdgeInsets.all(16.0), + child: Center(child: CircularProgressIndicator()), + ), + ], + ).padded(16), + ); + } + final stats = snapshot.data!; + return ExpansionCard( + title: Text( + "Statistics", //TODO: INTL + style: Theme.of(context).textTheme.titleLarge, + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: ListTile( + shape: Theme.of(context).cardTheme.shape, + titleTextStyle: Theme.of(context).textTheme.labelLarge, + title: Text("Documents in inbox:"), + onTap: () { + InboxRoute().go(context); + }, + trailing: Chip( + padding: EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), + labelPadding: EdgeInsets.symmetric(horizontal: 4), + label: Text( + stats.documentsInInbox.toString(), + ), + ), + ), + ), + Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: ListTile( + shape: Theme.of(context).cardTheme.shape, + titleTextStyle: Theme.of(context).textTheme.labelLarge, + title: Text("Total documents:"), + onTap: () { + DocumentsRoute().go(context); + }, + trailing: Chip( + padding: EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), + labelPadding: EdgeInsets.symmetric(horizontal: 4), + label: Text( + stats.documentsTotal.toString(), + ), + ), + ), + ), + Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: ListTile( + shape: Theme.of(context).cardTheme.shape, + titleTextStyle: Theme.of(context).textTheme.labelLarge, + title: Text("Total characters:"), + trailing: Chip( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), + labelPadding: EdgeInsets.symmetric(horizontal: 4), + label: Text( + stats.totalChars.toString(), + ), + ), + ), + ), + AspectRatio( + aspectRatio: 1.3, + child: SizedBox( + width: 300, + child: MimeTypesPieChart(statistics: stats), + ), + ), + ], + ).padded(16), + ); + }, + ); + } } diff --git a/lib/features/landing/view/widgets/expansion_card.dart b/lib/features/landing/view/widgets/expansion_card.dart new file mode 100644 index 0000000..15bb006 --- /dev/null +++ b/lib/features/landing/view/widgets/expansion_card.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:paperless_mobile/extensions/flutter_extensions.dart'; + +class ExpansionCard extends StatelessWidget { + final Widget title; + final Widget content; + + const ExpansionCard({super.key, required this.title, required this.content}); + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.all(16), + child: Theme( + data: Theme.of(context).copyWith( + dividerColor: Colors.transparent, + expansionTileTheme: ExpansionTileThemeData( + shape: Theme.of(context).cardTheme.shape, + collapsedShape: Theme.of(context).cardTheme.shape, + ), + listTileTheme: ListTileThemeData( + shape: Theme.of(context).cardTheme.shape, + ), + ), + child: ExpansionTile( + initiallyExpanded: true, + title: title, + children: [content], + ), + ), + ); + } +} diff --git a/lib/features/landing/view/widgets/mime_types_pie_chart.dart b/lib/features/landing/view/widgets/mime_types_pie_chart.dart new file mode 100644 index 0000000..81749b5 --- /dev/null +++ b/lib/features/landing/view/widgets/mime_types_pie_chart.dart @@ -0,0 +1,166 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:paperless_api/paperless_api.dart'; + +class MimeTypesPieChart extends StatefulWidget { + final PaperlessServerStatisticsModel statistics; + + const MimeTypesPieChart({ + super.key, + required this.statistics, + }); + + @override + State createState() => _MimeTypesPieChartState(); +} + +class _MimeTypesPieChartState extends State { + static final _mimeTypeNames = { + "application/pdf": "PDF Document", + "image/png": "PNG Image", + "image/jpeg": "JPEG Image", + "image/tiff": "TIFF Image", + "image/gif": "GIF Image", + "image/webp": "WebP Image", + "text/plain": "Plain Text Document", + "application/msword": "Microsoft Word Document", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + "Microsoft Word Document (OpenXML)", + "application/vnd.ms-powerpoint": "Microsoft PowerPoint Presentation", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": + "Microsoft PowerPoint Presentation (OpenXML)", + "application/vnd.ms-excel": "Microsoft Excel Spreadsheet", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + "Microsoft Excel Spreadsheet (OpenXML)", + "application/vnd.oasis.opendocument.text": "ODT Document", + "application/vnd.oasis.opendocument.presentation": "ODP Presentation", + "application/vnd.oasis.opendocument.spreadsheet": "ODS Spreadsheet", + }; + + int? _touchedIndex = -1; + + @override + Widget build(BuildContext context) { + final colorShades = Colors.lightGreen.values; + + return Column( + children: [ + Expanded( + child: PieChart( + PieChartData( + startDegreeOffset: 90, + // pieTouchData: PieTouchData( + // touchCallback: (event, response) { + // setState(() { + // if (!event.isInterestedForInteractions || + // response == null || + // response.touchedSection == null) { + // _touchedIndex = -1; + // return; + // } + // _touchedIndex = + // response.touchedSection!.touchedSectionIndex; + // }); + // }, + // ), + borderData: FlBorderData( + show: false, + ), + sectionsSpace: 0, + centerSpaceRadius: 40, + sections: _buildSections(colorShades).toList(), + ), + ), + ), + Wrap( + alignment: WrapAlignment.start, + spacing: 8, + runSpacing: 8, + children: [ + for (int i = 0; i < widget.statistics.fileTypeCounts.length; i++) + GestureDetector( + onTapDown: (_) { + setState(() { + _touchedIndex = i; + }); + }, + onTapUp: (details) { + setState(() { + _touchedIndex = -1; + }); + }, + onTapCancel: () { + setState(() { + _touchedIndex = -1; + }); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: colorShades[i % colorShades.length], + ), + margin: EdgeInsets.only(right: 8), + width: 20, + height: 20, + ), + Text( + _mimeTypeNames[ + widget.statistics.fileTypeCounts[i].mimeType]!, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ], + ), + ], + ); + } + + Iterable _buildSections(List colorShades) sync* { + for (int i = 0; i < widget.statistics.fileTypeCounts.length; i++) { + final type = widget.statistics.fileTypeCounts[i]; + final isTouched = i == _touchedIndex; + final fontSize = isTouched ? 24.0 : 16.0; + final radius = isTouched ? 60.0 : 50.0; + const shadows = [ + Shadow(color: Colors.black, blurRadius: 2), + ]; + final color = colorShades[i % colorShades.length]; + final textColor = + color.computeLuminance() > 0.5 ? Colors.black : Colors.white; + yield PieChartSectionData( + color: colorShades[i % colorShades.length], + value: type.count.toDouble(), + title: ((type.count / widget.statistics.documentsTotal) * 100) + .toStringAsFixed(1) + + "%", + radius: radius, + titleStyle: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + color: textColor, + ), + ); + } + } +} + +extension AllShades on MaterialColor { + List get values => [ + shade200, + shade600, + shade300, + shade100, + shade800, + shade400, + shade900, + shade500, + shade700, + ]; +} diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index d38de26..21c9ec4 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Lliure {bytes}", + "freeBytes": "Lliure {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -857,8 +857,12 @@ "@convertSinglePageScanToPdf": { "description": "description of the upload scans as pdf setting" }, - "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", + "loginRequiredPermissionsHint": "L'ús de Paperless Mobile requereix un conjunt mínim de permisos d'usuari des de paperless-ngx 1.14.0 i posterior. Per tant, assegureu-vos que l'usuari que voleu iniciar sessió té el permís per veure altres usuaris (Usuari → Visualització) i la configuració (UISettings → Visualització). Si no teniu aquests permisos, poseu-vos en contacte amb un administrador del vostre servidor paperless-ngx.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index 394defd..122eec3 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -81,7 +81,7 @@ "@createdAt": {}, "documentSuccessfullyDeleted": "Dokument byl úspěšně smazán.", "@documentSuccessfullyDeleted": {}, - "assignAsn": "Assign ASN", + "assignAsn": "Přiřadit ASČ", "@assignAsn": {}, "deleteDocumentTooltip": "Smazat", "@deleteDocumentTooltip": { @@ -129,13 +129,13 @@ }, "documentType": "Typ dokumentu", "@documentType": {}, - "archivedPdf": "Archived (pdf)", + "archivedPdf": "Archivováno (pdf)", "@archivedPdf": { "description": "Option to chose when downloading a document" }, "chooseFiletype": "Choose filetype", "@chooseFiletype": {}, - "original": "Original", + "original": "Originál", "@original": { "description": "Option to chose when downloading a document" }, @@ -580,7 +580,7 @@ "@done": {}, "next": "Další", "@next": {}, - "couldNotAccessReceivedFile": "Could not access the received file. Please try to open the app before sharing.", + "couldNotAccessReceivedFile": "Přístup k obdrženému souboru zamítnut. Než budeš sdílet, zkus nejdříve otevřít aplikaci.", "@couldNotAccessReceivedFile": {}, "newView": "Nový náhled", "@newView": {}, @@ -666,44 +666,44 @@ "@verifyYourIdentity": {}, "verifyIdentity": "Ověřit identitu", "@verifyIdentity": {}, - "detailed": "Detailed", + "detailed": "Detailně", "@detailed": {}, - "grid": "Grid", + "grid": "Mřížka", "@grid": {}, - "list": "List", + "list": "Seznam", "@list": {}, - "remove": "Remove", - "removeQueryFromSearchHistory": "Remove query from search history?", + "remove": "Odstranit", + "removeQueryFromSearchHistory": "Odstranit dotaz z historie vyhledávání?", "dynamicColorScheme": "Dynamicky", "@dynamicColorScheme": {}, "classicColorScheme": "Klasicky", "@classicColorScheme": {}, - "notificationDownloadComplete": "Download complete", + "notificationDownloadComplete": "Stahování dokončeno", "@notificationDownloadComplete": { "description": "Notification title when a download has been completed." }, - "notificationDownloadingDocument": "Downloading document", + "notificationDownloadingDocument": "Stahování dokumentu", "@notificationDownloadingDocument": { "description": "Notification title shown when a document download is pending" }, - "archiveSerialNumberUpdated": "Archive Serial Number updated.", + "archiveSerialNumberUpdated": "Archivní sériové číslo aktualizováno.", "@archiveSerialNumberUpdated": { "description": "Message shown when the ASN has been updated." }, - "donateCoffee": "Buy me a coffee", + "donateCoffee": "Kupte mi kávu", "@donateCoffee": { "description": "Label displayed in the app drawer" }, - "thisFieldIsRequired": "This field is required!", + "thisFieldIsRequired": "Toto pole je povinné!", "@thisFieldIsRequired": { "description": "Message shown below the form field when a required field has not been filled out." }, - "confirm": "Confirm", - "confirmAction": "Confirm action", + "confirm": "Potvrdit", + "confirmAction": "Potvrdit akci", "@confirmAction": { "description": "Typically used as a title to confirm a previously selected action" }, - "areYouSureYouWantToContinue": "Are you sure you want to continue?", + "areYouSureYouWantToContinue": "Opravdu chcete pokračovat?", "bulkEditTagsAddMessage": "{count, plural, one{This operation will add the tags {tags} to the selected document.} other{This operation will add the tags {tags} to {count} selected documents.}}", "@bulkEditTagsAddMessage": { "description": "Message of the confirmation dialog when bulk adding tags." @@ -722,39 +722,39 @@ "bulkEditCorrespondentRemoveMessage": "{count, plural, one{This operation will remove the correspondent from the selected document.} other{This operation will remove the correspondent from {count} selected documents.}}", "bulkEditDocumentTypeRemoveMessage": "{count, plural, one{This operation will remove the document type from the selected document.} other{This operation will remove the document type from {count} selected documents.}}", "bulkEditStoragePathRemoveMessage": "{count, plural, one{This operation will remove the storage path from the selected document.} other{This operation will remove the storage path from {count} selected documents.}}", - "anyTag": "Any", + "anyTag": "Jakékoliv", "@anyTag": { "description": "Label shown when any tag should be filtered" }, - "allTags": "All", + "allTags": "Všechny", "@allTags": { "description": "Label shown when a document has to be assigned to all selected tags" }, - "switchingAccountsPleaseWait": "Switching accounts. Please wait...", + "switchingAccountsPleaseWait": "Přepínání účtů. Počkejte prosím...", "@switchingAccountsPleaseWait": { "description": "Message shown while switching accounts is in progress." }, - "testConnection": "Test connection", + "testConnection": "Ověřit připojení", "@testConnection": { "description": "Button label shown on login page. Allows user to test whether the server is reachable or not." }, - "accounts": "Accounts", + "accounts": "Účty", "@accounts": { "description": "Title of the account management dialog" }, - "addAccount": "Add account", + "addAccount": "Přidat účet", "@addAccount": { "description": "Label of add account action" }, - "switchAccount": "Switch", + "switchAccount": "Přepnout", "@switchAccount": { "description": "Label for switch account action" }, - "logout": "Logout", + "logout": "Odhlásit", "@logout": { "description": "Generic Logout label" }, - "switchAccountTitle": "Switch account", + "switchAccountTitle": "Přepnout účet", "@switchAccountTitle": { "description": "Title of the dialog shown after adding an account, asking the user whether to switch to the newly added account or not." }, @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "Free {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 68744b4..5aac1d6 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "{bytes} freigeben", + "freeBytes": "{byteString} freigeben", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Die Verwendung von Paperless Mobile erfordert seit paperless-ngx 1.14.0 und höher ein Mindestmaß an Benutzerberechtigungen. Stelle deshalb bitte sicher, dass der anzumeldende Benutzer die Berechtigung hat, andere Benutzer (User → View) und die Einstellungen (UISettings → View) einzusehen. Falls du nicht über diese Berechtigungen verfügst, wende dich bitte an einen Administrator deines paperless-ngx Servers.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "Sie besitzen nicht die benötigten Berechtigungen, um diese Aktion durchzuführen.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b9620ca..0a941ba 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "Free {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 403f810..71dc73d 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -812,53 +812,57 @@ "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" }, - "export": "Export", + "export": "Exporter", "@export": { "description": "Label for button that exports scanned images to pdf (before upload)" }, - "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "invalidFilenameCharacter": "Caractère(s) invalide(s) trouvé dans le nom du fichier : {characters}", "@invalidFilenameCharacter": { "description": "For validating filename in export dialogue" }, - "exportScansToPdf": "Export scans to PDF", + "exportScansToPdf": "Exporter les scans en PDF", "@exportScansToPdf": { "description": "title of the alert dialog when exporting scans to pdf" }, - "allScansWillBeMerged": "All scans will be merged into a single PDF file.", - "behavior": "Behavior", + "allScansWillBeMerged": "Tous les scans seront fusionnés en un seul fichier PDF.", + "behavior": "Comportement", "@behavior": { "description": "Title of the settings concerning app beahvior" }, - "theme": "Theme", + "theme": "Thème", "@theme": { "description": "Title of the theme mode setting" }, - "clearCache": "Clear cache", + "clearCache": "Vider le cache", "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "{byteString} libres", "@freeBytes": { "description": "Text shown for clear storage settings" }, - "calculatingDots": "Calculating...", + "calculatingDots": "Calcul en cours...", "@calculatingDots": { "description": "Text shown when the byte size is still being calculated" }, - "freedDiskSpace": "Successfully freed {bytes} of disk space.", + "freedDiskSpace": "{bytes} d'espace disque libérés avec succès.", "@freedDiskSpace": { "description": "Message shown after clearing storage" }, - "uploadScansAsPdf": "Upload scans as PDF", + "uploadScansAsPdf": "Charger les scans au format PDF", "@uploadScansAsPdf": { "description": "Title of the setting which toggles whether scans are always uploaded as pdf" }, - "convertSinglePageScanToPdf": "Always convert single page scans to PDF before uploading", + "convertSinglePageScanToPdf": "Toujours convertir les scans d'une page en PDF avant de charger le document", "@convertSinglePageScanToPdf": { "description": "description of the upload scans as pdf setting" }, - "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", + "loginRequiredPermissionsHint": "L'utilisation de Paperless Mobile nécessite un ensemble minimal d'autorisations utilisateur depuis la version 1.14.0 et supérieure. Par conséquent, assurez-vous que l'utilisateur connecté a la permission de voir les autres utilisateurs (Utilisateur → Affichage) et les paramètres (UISettings → Affichage). Si vous ne disposez pas de ces autorisations, veuillez contacter un administrateur de votre serveur paperless-ngx.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index db13285..a6b5d0f 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "Free {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 2abc032..a893206 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -21,11 +21,11 @@ "@addStoragePath": { "description": "Title when adding a new storage path" }, - "addTag": "New Tag", + "addTag": "Новый запрос", "@addTag": { "description": "Title when adding a new tag" }, - "aboutThisApp": "About this app", + "aboutThisApp": "О приложении", "@aboutThisApp": { "description": "Label for about this app tile displayed in the drawer" }, @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "Free {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index 183a5a1..ea52809 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -837,7 +837,7 @@ "@clearCache": { "description": "Title of the clear cache setting" }, - "freeBytes": "Free {bytes}", + "freeBytes": "Free {byteString}", "@freeBytes": { "description": "Text shown for clear storage settings" }, @@ -860,5 +860,9 @@ "loginRequiredPermissionsHint": "Using Paperless Mobile requires a minimum set of user permissions since paperless-ngx 1.14.0 and higher. Therefore, please make sure that the user to be logged in has the permission to view other users (User → View) and the settings (UISettings → View). If you do not have these permissions, please contact an administrator of your paperless-ngx server.", "@loginRequiredPermissionsHint": { "description": "Hint shown on the login page informing the user of the required permissions to use the app." + }, + "missingPermissions": "You do not have the necessary permissions to perform this action.", + "@missingPermissions": { + "description": "Message shown in a snackbar when a user without the reequired permissions performs an action." } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 5c14e10..8b7a1e9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -257,7 +257,10 @@ class _GoRouterShellState extends State { // ), // ], // ), - StatefulShellRoute.indexedStack( + StatefulShellRoute( + navigatorContainerBuilder: (context, navigationShell, children) { + return children[navigationShell.currentIndex]; + }, builder: const ScaffoldShellRoute().builder, branches: [ StatefulShellBranch( diff --git a/lib/theme.dart b/lib/theme.dart index d1543ba..34c3731 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -8,6 +8,12 @@ const _defaultListTileTheme = ListTileThemeData( tileColor: Colors.transparent, ); +final _defaultCardTheme = CardTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), +); + final _defaultInputDecorationTheme = InputDecorationTheme( border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), @@ -40,6 +46,7 @@ ThemeData buildTheme({ colorScheme: colorScheme.harmonized(), useMaterial3: true, ).copyWith( + cardTheme: _defaultCardTheme, inputDecorationTheme: _defaultInputDecorationTheme, listTileTheme: _defaultListTileTheme, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/packages/paperless_api/lib/src/models/paperless_server_statistics_model.dart b/packages/paperless_api/lib/src/models/paperless_server_statistics_model.dart index 77cd188..26bfbc3 100644 --- a/packages/paperless_api/lib/src/models/paperless_server_statistics_model.dart +++ b/packages/paperless_api/lib/src/models/paperless_server_statistics_model.dart @@ -1,13 +1,34 @@ class PaperlessServerStatisticsModel { final int documentsTotal; final int documentsInInbox; - + final int? totalChars; + final List fileTypeCounts; PaperlessServerStatisticsModel({ required this.documentsTotal, required this.documentsInInbox, + this.totalChars, + this.fileTypeCounts = const [], }); PaperlessServerStatisticsModel.fromJson(Map json) : documentsTotal = json['documents_total'] ?? 0, - documentsInInbox = json['documents_inbox'] ?? 0; + documentsInInbox = json['documents_inbox'] ?? 0, + totalChars = json["character_count"], + fileTypeCounts = (json['document_file_type_counts'] as List? ?? []) + .map((e) => DocumentFileTypeCount.fromJson(e)) + .toList(); +} + +class DocumentFileTypeCount { + final String mimeType; + final int count; + + DocumentFileTypeCount({ + required this.mimeType, + required this.count, + }); + + DocumentFileTypeCount.fromJson(Map json) + : mimeType = json['mime_type'], + count = json['mime_type_count']; } diff --git a/pubspec.lock b/pubspec.lock index 7c38544..7585a75 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -458,6 +458,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: c1e26c7e48496be85104c16c040950b0436674cdf0737f3f6e95511b2529b592 + url: "https://pub.dev" + source: hosted + version: "0.63.0" flutter: dependency: "direct main" description: flutter @@ -1108,6 +1116,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + palette_generator: + dependency: "direct main" + description: + name: palette_generator + sha256: "0e3cd6974e10b1434dcf4cf779efddb80e2696585e273a2dbede6af52f94568d" + url: "https://pub.dev" + source: hosted + version: "0.3.3+2" paperless_api: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index cba6496..69088be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,6 +91,8 @@ dependencies: printing: ^5.11.0 flutter_pdfview: ^1.3.1 go_router: ^10.0.0 + fl_chart: ^0.63.0 + palette_generator: ^0.3.3+2 dependency_overrides: intl: ^0.18.1