diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4001596..9498ab3 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
- >
+ >
+
@@ -162,11 +163,22 @@
+
+
+
+
+
+
+
+
+
+
+
()).connectivityChanges())
-// .thenAnswer((i) => Stream.value(true));
-// when((getIt() as MockLocalVault)
-// .loadAuthenticationInformation())
-// .thenAnswer((realInvocation) async => null);
-// when((getIt() as MockLocalVault).loadApplicationSettings())
-// .thenAnswer((realInvocation) async => ApplicationSettingsState(
-// preferredLocaleSubtag: 'en',
-// preferredThemeMode: ThemeMode.light,
-// isLocalAuthenticationEnabled: false,
-// preferredViewType: ViewType.list,
-// showInboxOnStartup: false,
-// ));
-// when(getIt().login(
-// username: testUsername,
-// password: testPassword,
-// )).thenAnswer((i) => Future.value("eyTestToken"));
+class MockSessionManager extends Mock implements SessionManager {}
-// await getIt().initialize();
-// await getIt().initialize();
-// });
+class MockLocalNotificationService extends Mock
+ implements LocalNotificationService {}
-// // Mocked classes
+void main() async {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ const locale = Locale("en", "US");
+ const testServerUrl = 'https://example.com';
+ const testUsername = 'user';
+ const testPassword = 'pass';
-// await t.binding.waitUntilFirstFrameRasterized;
-// await tester.pumpAndSettle();
+ final hiveDirectory = await getTemporaryDirectory();
-// await tester.enterText(serverAddressField, testServerUrl);
-// await tester.pumpAndSettle();
+ late ConnectivityStatusService connectivityStatusService;
+ late MockPaperlessApiFactory paperlessApiFactory;
+ late AuthenticationCubit authenticationCubit;
+ late LocalNotificationService localNotificationService;
+ late SessionManager sessionManager;
+ final localAuthService = MockLocalAuthService();
-// await tester.enterText(usernameField, testUsername);
-// await tester.pumpAndSettle();
+ setUp(() async {
+ connectivityStatusService = MockConnectivityStatusService();
+ paperlessApiFactory = MockPaperlessApiFactory();
+ sessionManager = MockSessionManager();
+ localNotificationService = MockLocalNotificationService();
-// await tester.enterText(passwordField, testPassword);
+ authenticationCubit = AuthenticationCubit(
+ localAuthService,
+ paperlessApiFactory,
+ sessionManager,
+ connectivityStatusService,
+ localNotificationService,
+ );
+ await initHive(
+ hiveDirectory,
+ locale.toString(),
+ );
+ });
+ testWidgets(
+ 'A user shall be successfully logged in when providing correct credentials.',
+ (tester) async {
+ // Reset data to initial state with given [locale].
+ await Hive.globalSettingsBox.setValue(
+ GlobalSettings(
+ preferredLocaleSubtag: locale.toString(),
+ loggedInUserId: null,
+ ),
+ );
+ when(paperlessApiFactory.authenticationApi.login(
+ username: testUsername,
+ password: testPassword,
+ )).thenAnswer((_) async => "token");
-// FocusManager.instance.primaryFocus?.unfocus();
-// await tester.pumpAndSettle();
+ await initializeDefaultParameters();
-// await tester.tap(loginBtn);
+ await tester.pumpWidget(
+ AppEntrypoint(
+ apiFactory: paperlessApiFactory,
+ authenticationCubit: authenticationCubit,
+ connectivityStatusService: connectivityStatusService,
+ localNotificationService: localNotificationService,
+ localAuthService: localAuthService,
+ sessionManager: sessionManager,
+ ),
+ );
+ await tester.binding.waitUntilFirstFrameRasterized;
+ await tester.pumpAndSettle();
-// verify(getIt().login(
-// username: testUsername,
-// password: testPassword,
-// )).called(1);
-// });
+ await tester.enterText(
+ find.byKey(TestKeys.login.serverAddressFormField),
+ testServerUrl,
+ );
+ await tester.pumpAndSettle();
-// testWidgets('Test login validation missing password',
-// (WidgetTester tester) async {
-// await initAndLaunchTestApp(tester, () async {
-// when((getIt() as MockConnectivityStatusService)
-// .connectivityChanges())
-// .thenAnswer((i) => Stream.value(true));
-// when((getIt() as MockLocalVault)
-// .loadAuthenticationInformation())
-// .thenAnswer((realInvocation) async => null);
+ await tester.press(find.byKey(TestKeys.login.continueButton));
-// when((getIt() as MockLocalVault).loadApplicationSettings())
-// .thenAnswer((realInvocation) async => ApplicationSettingsState(
-// preferredLocaleSubtag: 'en',
-// preferredThemeMode: ThemeMode.light,
-// isLocalAuthenticationEnabled: false,
-// preferredViewType: ViewType.list,
-// showInboxOnStartup: false,
-// ));
+ await tester.pumpAndSettle();
+ expect(
+ find.byKey(TestKeys.login.usernameFormField),
+ findsOneWidget,
+ );
-// await getIt().initialize();
-// await getIt().initialize();
-// });
-// // Mocked classes
+ await tester.enterText(
+ find.byKey(TestKeys.login.usernameFormField),
+ testUsername,
+ );
+ await tester.enterText(
+ find.byKey(TestKeys.login.passwordFormField),
+ testUsername,
+ );
+ await tester.pumpAndSettle();
-// // Initialize dat for mocked classes
+ await tester.press(find.byKey(TestKeys.login.loginButton));
+ await tester.pumpAndSettle();
-// await t.binding.waitUntilFirstFrameRasterized;
-// await tester.pumpAndSettle();
-
-// await tester.enterText(serverAddressField, testServerUrl);
-// await tester.pumpAndSettle();
-
-// await tester.enterText(usernameField, testUsername);
-// await tester.pumpAndSettle();
-
-// FocusManager.instance.primaryFocus?.unfocus();
-// await tester.pumpAndSettle();
-
-// await tester.tap(loginBtn);
-// await tester.pumpAndSettle();
-
-// verifyNever(
-// (getIt() as MockPaperlessAuthenticationApi)
-// .login(
-// username: testUsername,
-// password: testPassword,
-// ));
-// expect(
-// find.textContaining(t.translations.passwordMustNotBeEmpty),
-// findsOneWidget,
-// );
-// });
-
-// testWidgets('Test login validation missing username',
-// (WidgetTester tester) async {
-// await initAndLaunchTestApp(tester, () async {
-// when((getIt() as MockConnectivityStatusService)
-// .connectivityChanges())
-// .thenAnswer((i) => Stream.value(true));
-// when((getIt() as MockLocalVault)
-// .loadAuthenticationInformation())
-// .thenAnswer((realInvocation) async => null);
-// when((getIt() as MockLocalVault).loadApplicationSettings())
-// .thenAnswer((realInvocation) async => ApplicationSettingsState(
-// preferredLocaleSubtag: 'en',
-// preferredThemeMode: ThemeMode.light,
-// isLocalAuthenticationEnabled: false,
-// preferredViewType: ViewType.list,
-// showInboxOnStartup: false,
-// ));
-// await getIt().initialize();
-// await getIt().initialize();
-// });
-
-// await t.binding.waitUntilFirstFrameRasterized;
-// await tester.pumpAndSettle();
-
-// await tester.enterText(serverAddressField, testServerUrl);
-// await tester.pumpAndSettle();
-
-// await tester.enterText(passwordField, testPassword);
-// await tester.pumpAndSettle();
-
-// FocusManager.instance.primaryFocus?.unfocus();
-// await tester.pumpAndSettle();
-
-// await tester.tap(loginBtn);
-// await tester.pumpAndSettle();
-
-// verifyNever(
-// (getIt() as MockPaperlessAuthenticationApi)
-// .login(
-// username: testUsername,
-// password: testPassword,
-// ));
-// expect(
-// find.textContaining(t.translations.usernameMustNotBeEmpty),
-// findsOneWidget,
-// );
-// });
-
-// testWidgets('Test login validation missing server address',
-// (WidgetTester tester) async {
-// initAndLaunchTestApp(tester, () async {
-// when((getIt()).connectivityChanges())
-// .thenAnswer((i) => Stream.value(true));
-
-// when((getIt()).loadAuthenticationInformation())
-// .thenAnswer((realInvocation) async => null);
-
-// when((getIt()).loadApplicationSettings())
-// .thenAnswer((realInvocation) async => ApplicationSettingsState(
-// preferredLocaleSubtag: 'en',
-// preferredThemeMode: ThemeMode.light,
-// isLocalAuthenticationEnabled: false,
-// preferredViewType: ViewType.list,
-// showInboxOnStartup: false,
-// ));
-
-// await getIt().initialize();
-// await getIt().initialize();
-// });
-
-// await t.binding.waitUntilFirstFrameRasterized;
-// await tester.pumpAndSettle();
-
-// await tester.enterText(usernameField, testUsername);
-// await tester.pumpAndSettle();
-
-// await tester.enterText(passwordField, testPassword);
-// await tester.pumpAndSettle();
-
-// FocusManager.instance.primaryFocus?.unfocus();
-// await tester.pumpAndSettle();
-
-// await tester.tap(loginBtn);
-// await tester.pumpAndSettle();
-
-// verifyNever(getIt().login(
-// username: testUsername,
-// password: testPassword,
-// ));
-// expect(
-// find.textContaining(
-// t.translations.loginPageServerUrlValidatorMessageText),
-// findsOneWidget,
-// );
-// });
-// }
+ expect(
+ find.byKey(TestKeys.login.loggingInScreen),
+ findsOneWidget,
+ );
+ });
+}
diff --git a/integration_test/src/framework.dart b/integration_test/src/framework.dart
index ef95f48..9311c92 100644
--- a/integration_test/src/framework.dart
+++ b/integration_test/src/framework.dart
@@ -1,7 +1,16 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
+import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
+import 'package:paperless_mobile/core/security/session_manager.dart';
+import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
+import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
+import 'package:paperless_mobile/features/login/services/authentication_service.dart';
+import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
+import 'package:paperless_mobile/main.dart'
+ show initializeDefaultParameters, AppEntrypoint;
+import 'package:path_provider/path_provider.dart';
Future initializeTestingFramework(
{String languageCode = 'en'}) async {
@@ -26,11 +35,3 @@ class TestingFrameworkVariables {
required this.translations,
});
}
-
-Future initAndLaunchTestApp(
- WidgetTester tester,
- Future Function() initializationCallback,
-) async {
- await initializationCallback();
- //runApp(const PaperlessMobileEntrypoint(authenticationCubit: ),));
-}
diff --git a/integration_test/src/mocks/mock_paperless_api.dart b/integration_test/src/mocks/mock_paperless_api.dart
new file mode 100644
index 0000000..81f503e
--- /dev/null
+++ b/integration_test/src/mocks/mock_paperless_api.dart
@@ -0,0 +1,65 @@
+import 'package:dio/src/dio.dart';
+import 'package:paperless_api/paperless_api.dart';
+import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
+import 'package:mockito/annotations.dart';
+
+@GenerateNiceMocks([
+ MockSpec(),
+ MockSpec(),
+ MockSpec(),
+ MockSpec(),
+ MockSpec(),
+ MockSpec(),
+ MockSpec(),
+])
+import 'mock_paperless_api.mocks.dart';
+
+class MockPaperlessApiFactory implements PaperlessApiFactory {
+ final PaperlessAuthenticationApi authenticationApi =
+ MockPaperlessAuthenticationApi();
+ final PaperlessDocumentsApi documentApi = MockPaperlessDocumentsApi();
+ final PaperlessLabelsApi labelsApi = MockPaperlessLabelsApi();
+ final PaperlessUserApi userApi = MockPaperlessUserApi();
+ final PaperlessSavedViewsApi savedViewsApi = MockPaperlessSavedViewsApi();
+ final PaperlessServerStatsApi serverStatsApi = MockPaperlessServerStatsApi();
+ final PaperlessTasksApi tasksApi = MockPaperlessTasksApi();
+
+ @override
+ PaperlessAuthenticationApi createAuthenticationApi(Dio dio) {
+ return authenticationApi;
+ }
+
+ @override
+ PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion}) {
+ return documentApi;
+ }
+
+ @override
+ PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion}) {
+ return labelsApi;
+ }
+
+ @override
+ PaperlessSavedViewsApi createSavedViewsApi(
+ Dio dio, {
+ required int apiVersion,
+ }) {
+ return savedViewsApi;
+ }
+
+ @override
+ PaperlessServerStatsApi createServerStatsApi(Dio dio,
+ {required int apiVersion}) {
+ return serverStatsApi;
+ }
+
+ @override
+ PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion}) {
+ return tasksApi;
+ }
+
+ @override
+ PaperlessUserApi createUserApi(Dio dio, {required int apiVersion}) {
+ return userApi;
+ }
+}
diff --git a/lib/constants.dart b/lib/constants.dart
index 2a86892..cac12c5 100644
--- a/lib/constants.dart
+++ b/lib/constants.dart
@@ -5,3 +5,5 @@ import 'package:package_info_plus/package_info_plus.dart';
late final PackageInfo packageInfo;
late final AndroidDeviceInfo? androidInfo;
late final IosDeviceInfo? iosInfo;
+
+const latestSupportedApiVersion = 3;
diff --git a/lib/core/bloc/base_state.dart b/lib/core/bloc/base_state.dart
new file mode 100644
index 0000000..6e16240
--- /dev/null
+++ b/lib/core/bloc/base_state.dart
@@ -0,0 +1,25 @@
+import 'package:paperless_mobile/core/bloc/loading_status.dart';
+
+class BaseState {
+ final Object? error;
+ final T? value;
+ final LoadingStatus status;
+
+ BaseState({
+ required this.error,
+ required this.value,
+ required this.status,
+ });
+
+ BaseState copyWith({
+ Object? error,
+ T? value,
+ LoadingStatus? status,
+ }) {
+ return BaseState(
+ error: error ?? this.error,
+ value: value ?? this.value,
+ status: status ?? this.status,
+ );
+ }
+}
diff --git a/lib/core/database/hive/hive_config.dart b/lib/core/database/hive/hive_config.dart
index 38eb172..7705233 100644
--- a/lib/core/database/hive/hive_config.dart
+++ b/lib/core/database/hive/hive_config.dart
@@ -19,12 +19,14 @@ class HiveBoxes {
static const localUserAccount = 'localUserAccount';
static const localUserAppState = 'localUserAppState';
static const hosts = 'hosts';
+ static const hintStateBox = 'hintStateBox';
static List get all => [
globalSettings,
localUserCredentials,
localUserAccount,
localUserAppState,
+ hintStateBox,
hosts,
];
}
diff --git a/lib/core/database/hive/hive_extensions.dart b/lib/core/database/hive/hive_extensions.dart
index 6288eac..e3b2193 100644
--- a/lib/core/database/hive/hive_extensions.dart
+++ b/lib/core/database/hive/hive_extensions.dart
@@ -54,4 +54,5 @@ extension HiveBoxAccessors on HiveInterface {
box(HiveBoxes.localUserAppState);
Box get globalSettingsBox =>
box(HiveBoxes.globalSettings);
+ Box get hintStateBox => box(HiveBoxes.hintStateBox);
}
diff --git a/lib/core/database/hive/hive_initialization.dart b/lib/core/database/hive/hive_initialization.dart
new file mode 100644
index 0000000..e5988da
--- /dev/null
+++ b/lib/core/database/hive/hive_initialization.dart
@@ -0,0 +1,25 @@
+import 'dart:io';
+
+import 'package:hive/hive.dart';
+import 'package:hive_flutter/adapters.dart';
+import 'package:paperless_mobile/core/database/hive/hive_config.dart';
+import 'package:paperless_mobile/core/database/tables/global_settings.dart';
+import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
+import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
+
+Future initHive(Directory directory, String defaultLocale) async {
+ Hive.init(directory.path);
+ registerHiveAdapters();
+ await Hive.openBox(HiveBoxes.localUserAccount);
+ await Hive.openBox(HiveBoxes.localUserAppState);
+ await Hive.openBox(HiveBoxes.hintStateBox);
+ await Hive.openBox(HiveBoxes.hosts);
+ final globalSettingsBox =
+ await Hive.openBox(HiveBoxes.globalSettings);
+
+ if (!globalSettingsBox.hasValue) {
+ await globalSettingsBox.setValue(
+ GlobalSettings(preferredLocaleSubtag: defaultLocale),
+ );
+ }
+}
diff --git a/lib/core/extensions/flutter_extensions.dart b/lib/core/extensions/flutter_extensions.dart
index a4cd2e9..ebbee0c 100644
--- a/lib/core/extensions/flutter_extensions.dart
+++ b/lib/core/extensions/flutter_extensions.dart
@@ -11,9 +11,18 @@ extension WidgetPadding on Widget {
Widget paddedSymmetrically({
double horizontal = 0.0,
double vertical = 0.0,
+ bool sliver = false,
}) {
+ final insets =
+ EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical);
+ if (sliver) {
+ return SliverPadding(
+ padding: insets,
+ sliver: this,
+ );
+ }
return Padding(
- padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
+ padding: insets,
child: this,
);
}
diff --git a/lib/core/interceptor/language_header.interceptor.dart b/lib/core/interceptor/language_header.interceptor.dart
index 4d81272..862d037 100644
--- a/lib/core/interceptor/language_header.interceptor.dart
+++ b/lib/core/interceptor/language_header.interceptor.dart
@@ -1,16 +1,16 @@
import 'package:dio/dio.dart';
class LanguageHeaderInterceptor extends Interceptor {
- String preferredLocaleSubtag;
- LanguageHeaderInterceptor(this.preferredLocaleSubtag);
+ final String Function() preferredLocaleSubtagBuilder;
+ LanguageHeaderInterceptor(this.preferredLocaleSubtagBuilder);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
late String languages;
- if (preferredLocaleSubtag == "en") {
+ if (preferredLocaleSubtagBuilder() == "en") {
languages = "en";
} else {
- languages = "$preferredLocaleSubtag,en;q=0.7,en-US;q=0.6";
+ languages = "${preferredLocaleSubtagBuilder()},en;q=0.7,en-US;q=0.6";
}
options.headers.addAll({"Accept-Language": languages});
handler.next(options);
diff --git a/lib/core/model/info_message_exception.dart b/lib/core/model/info_message_exception.dart
index 817954e..36ddafa 100644
--- a/lib/core/model/info_message_exception.dart
+++ b/lib/core/model/info_message_exception.dart
@@ -9,4 +9,9 @@ class InfoMessageException implements Exception {
this.message,
this.stackTrace,
});
+
+ @override
+ String toString() {
+ return 'InfoMessageException(code: $code, message: $message, stackTrace: $stackTrace)';
+ }
}
diff --git a/lib/core/security/session_manager.dart b/lib/core/security/session_manager.dart
index 2244d34..50877d6 100644
--- a/lib/core/security/session_manager.dart
+++ b/lib/core/security/session_manager.dart
@@ -1,93 +1,14 @@
-import 'dart:io';
-
import 'package:dio/dio.dart';
-import 'package:dio/io.dart';
import 'package:flutter/material.dart';
-import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
-import 'package:paperless_mobile/core/interceptor/dio_offline_interceptor.dart';
-import 'package:paperless_mobile/core/interceptor/dio_unauthorized_interceptor.dart';
-import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
-import 'package:pretty_dio_logger/pretty_dio_logger.dart';
-/// Manages the security context, authentication and base request URL for
-/// an underlying [Dio] client which is injected into all services
-/// requiring authenticated access to the Paperless REST API.
-class SessionManager extends ValueNotifier {
- Dio get client => value;
-
- SessionManager([List interceptors = const []])
- : super(_initDio(interceptors));
-
- static Dio _initDio(List interceptors) {
- //en- and decoded by utf8 by default
- final Dio dio = Dio(
- BaseOptions(
- contentType: Headers.jsonContentType,
- followRedirects: true,
- maxRedirects: 10,
- ),
- );
- dio.options
- ..receiveTimeout = const Duration(seconds: 30)
- ..sendTimeout = const Duration(seconds: 60)
- ..responseType = ResponseType.json;
- (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient =
- () => HttpClient()..badCertificateCallback = (cert, host, port) => true;
- dio.interceptors.addAll([
- ...interceptors,
- DioUnauthorizedInterceptor(),
- DioHttpErrorInterceptor(),
- DioOfflineInterceptor(),
- RetryOnConnectionChangeInterceptor(dio: dio)
- ]);
- return dio;
- }
+abstract interface class SessionManager implements ChangeNotifier {
+ Dio get client;
void updateSettings({
String? baseUrl,
String? authToken,
ClientCertificate? clientCertificate,
- }) {
- if (clientCertificate != null) {
- final context = SecurityContext()
- ..usePrivateKeyBytes(
- clientCertificate.bytes,
- password: clientCertificate.passphrase,
- )
- ..useCertificateChainBytes(
- clientCertificate.bytes,
- password: clientCertificate.passphrase,
- )
- ..setTrustedCertificatesBytes(
- clientCertificate.bytes,
- password: clientCertificate.passphrase,
- );
- final adapter = IOHttpClientAdapter()
- ..createHttpClient = () => HttpClient(context: context)
- ..badCertificateCallback =
- (X509Certificate cert, String host, int port) => true;
-
- client.httpClientAdapter = adapter;
- }
-
- if (baseUrl != null) {
- client.options.baseUrl = baseUrl;
- }
-
- if (authToken != null) {
- client.options.headers.addAll({
- HttpHeaders.authorizationHeader: 'Token $authToken',
- });
- }
-
- notifyListeners();
- }
-
- void resetSettings() {
- client.httpClientAdapter = IOHttpClientAdapter();
- client.options.baseUrl = '';
- client.options.headers.remove(HttpHeaders.authorizationHeader);
- notifyListeners();
- }
+ });
+ void resetSettings();
}
diff --git a/lib/core/security/session_manager_impl.dart b/lib/core/security/session_manager_impl.dart
new file mode 100644
index 0000000..c9a5491
--- /dev/null
+++ b/lib/core/security/session_manager_impl.dart
@@ -0,0 +1,96 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:dio/io.dart';
+import 'package:flutter/material.dart';
+import 'package:paperless_api/paperless_api.dart';
+import 'package:paperless_mobile/core/interceptor/dio_offline_interceptor.dart';
+import 'package:paperless_mobile/core/interceptor/dio_unauthorized_interceptor.dart';
+import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
+import 'package:paperless_mobile/core/security/session_manager.dart';
+import 'package:paperless_mobile/features/login/model/client_certificate.dart';
+
+/// Manages the security context, authentication and base request URL for
+/// an underlying [Dio] client which is injected into all services
+/// requiring authenticated access to the Paperless REST API.
+class SessionManagerImpl extends ValueNotifier implements SessionManager {
+ @override
+ Dio get client => value;
+
+ SessionManagerImpl([List interceptors = const []])
+ : super(_initDio(interceptors));
+
+ static Dio _initDio(List interceptors) {
+ //en- and decoded by utf8 by default
+ final Dio dio = Dio(
+ BaseOptions(
+ contentType: Headers.jsonContentType,
+ followRedirects: true,
+ maxRedirects: 10,
+ ),
+ );
+ dio.options
+ ..receiveTimeout = const Duration(seconds: 30)
+ ..sendTimeout = const Duration(seconds: 60)
+ ..responseType = ResponseType.json;
+ (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient =
+ () => HttpClient()..badCertificateCallback = (cert, host, port) => true;
+ dio.interceptors.addAll([
+ ...interceptors,
+ DioUnauthorizedInterceptor(),
+ DioHttpErrorInterceptor(),
+ DioOfflineInterceptor(),
+ RetryOnConnectionChangeInterceptor(dio: dio)
+ ]);
+ return dio;
+ }
+
+ @override
+ void updateSettings({
+ String? baseUrl,
+ String? authToken,
+ ClientCertificate? clientCertificate,
+ }) {
+ if (clientCertificate != null) {
+ final context = SecurityContext()
+ ..usePrivateKeyBytes(
+ clientCertificate.bytes,
+ password: clientCertificate.passphrase,
+ )
+ ..useCertificateChainBytes(
+ clientCertificate.bytes,
+ password: clientCertificate.passphrase,
+ )
+ ..setTrustedCertificatesBytes(
+ clientCertificate.bytes,
+ password: clientCertificate.passphrase,
+ );
+ final adapter = IOHttpClientAdapter()
+ ..createHttpClient = () => HttpClient(context: context)
+ ..badCertificateCallback =
+ (X509Certificate cert, String host, int port) => true;
+
+ client.httpClientAdapter = adapter;
+ }
+
+ if (baseUrl != null) {
+ client.options.baseUrl = baseUrl;
+ }
+
+ if (authToken != null) {
+ client.options.headers.addAll({
+ HttpHeaders.authorizationHeader: 'Token $authToken',
+ });
+ }
+
+ notifyListeners();
+ }
+
+ @override
+ void resetSettings() {
+ client.httpClientAdapter = IOHttpClientAdapter();
+ client.options.baseUrl = '';
+ client.options.headers.remove(HttpHeaders.authorizationHeader);
+ notifyListeners();
+ }
+}
diff --git a/lib/core/service/connectivity_status_service.dart b/lib/core/service/connectivity_status_service.dart
index 6ce404b..0e95a2f 100644
--- a/lib/core/service/connectivity_status_service.dart
+++ b/lib/core/service/connectivity_status_service.dart
@@ -5,6 +5,7 @@ import 'package:dio/dio.dart';
import 'package:paperless_mobile/core/global/os_error_codes.dart';
import 'package:paperless_mobile/core/interceptor/server_reachability_error_interceptor.dart';
import 'package:paperless_mobile/core/security/session_manager.dart';
+import 'package:paperless_mobile/core/security/session_manager_impl.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
import 'package:rxdart/subjects.dart';
@@ -79,7 +80,7 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
}
try {
SessionManager manager =
- SessionManager([ServerReachabilityErrorInterceptor()])
+ SessionManagerImpl([ServerReachabilityErrorInterceptor()])
..updateSettings(clientCertificate: clientCertificate)
..client.options.connectTimeout = const Duration(seconds: 5)
..client.options.receiveTimeout = const Duration(seconds: 5);
diff --git a/lib/core/translation/error_code_localization_mapper.dart b/lib/core/translation/error_code_localization_mapper.dart
index 499c08e..2fc69f4 100644
--- a/lib/core/translation/error_code_localization_mapper.dart
+++ b/lib/core/translation/error_code_localization_mapper.dart
@@ -82,5 +82,7 @@ String translateError(BuildContext context, ErrorCode code) {
'Could not load custom field.', //TODO: INTL
ErrorCode.customFieldDeleteFailed =>
'Could not delete custom field, please try again.', //TODO: INTL
+ ErrorCode.deleteNoteFailed => 'Could not delete note, please try again.',
+ ErrorCode.addNoteFailed => 'Could not create note, please try again.',
};
}
diff --git a/lib/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart b/lib/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart
index eb1ab5f..6fb020c 100644
--- a/lib/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart
+++ b/lib/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart
@@ -3,9 +3,11 @@
import 'dart:collection';
import 'package:collection/collection.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart';
@@ -83,7 +85,6 @@ class _FormBuilderLocalizedDatePickerState
final _textFieldControls =
LinkedList<_NeighbourAwareDateInputSegmentControls>();
- String? _error;
bool _temporarilyDisableListeners = false;
@override
void initState() {
@@ -184,10 +185,7 @@ class _FormBuilderLocalizedDatePickerState
// Imitate the functionality of the validator function in "normal" form fields.
// The error is shown on the outer decorator as if this was a regular text input.
// Errors are cleared after the next user interaction.
- final error = _validateDate(value);
- setState(() {
- _error = error;
- });
+ // final error = _validateDate(value);
},
autovalidateMode: AutovalidateMode.onUserInteraction,
initialValue: widget.initialValue != null
@@ -201,7 +199,7 @@ class _FormBuilderLocalizedDatePickerState
child: InputDecorator(
textAlignVertical: TextAlignVertical.bottom,
decoration: InputDecoration(
- errorText: _error,
+ errorText: field.errorText,
labelText: widget.labelText,
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
@@ -271,16 +269,10 @@ class _FormBuilderLocalizedDatePickerState
if (d.day != date.day && d.month != date.month && d.year != date.year) {
return "Invalid date.";
}
- if (d.isBefore(widget.firstDate)) {
- final formattedDateHint =
- DateFormat.yMd(widget.locale.toString()).format(widget.firstDate);
- return "Date must be after $formattedDateHint.";
- }
- if (d.isAfter(widget.lastDate)) {
- final formattedDateHint =
- DateFormat.yMd(widget.locale.toString()).format(widget.lastDate);
- return "Date must be before $formattedDateHint.";
+ if (d.isBefore(widget.firstDate) || d.isAfter(widget.lastDate)) {
+ return S.of(context)!.dateOutOfRange(widget.firstDate, widget.lastDate);
}
+
return null;
}
@@ -332,6 +324,7 @@ class _FormBuilderLocalizedDatePickerState
_DateInputSegment.year => fieldValue.copyWith(year: number),
};
field.setValue(newValue);
+ field.validate();
}
},
inputFormatters: [
diff --git a/lib/core/widgets/hint_card.dart b/lib/core/widgets/hint_card.dart
index 195473f..7a94df3 100644
--- a/lib/core/widgets/hint_card.dart
+++ b/lib/core/widgets/hint_card.dart
@@ -61,7 +61,7 @@ class HintCard extends StatelessWidget {
const Padding(padding: EdgeInsets.only(bottom: 24)),
],
).padded(),
- ).padded(),
+ ),
);
}
}
diff --git a/lib/core/widgets/hint_state_builder.dart b/lib/core/widgets/hint_state_builder.dart
new file mode 100644
index 0000000..22a636e
--- /dev/null
+++ b/lib/core/widgets/hint_state_builder.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+import 'package:hive_flutter/adapters.dart';
+import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
+
+class HintStateBuilder extends StatelessWidget {
+ final String? listenKey;
+ final Widget Function(BuildContext context, Box box) builder;
+ const HintStateBuilder({
+ super.key,
+ required this.builder,
+ this.listenKey,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder>(
+ valueListenable: Hive.hintStateBox
+ .listenable(keys: listenKey != null ? [listenKey] : null),
+ builder: (context, box, child) {
+ return builder(context, box);
+ },
+ );
+ }
+}
diff --git a/lib/features/changelogs/view/changelog_dialog.dart b/lib/features/changelogs/view/changelog_dialog.dart
index a9804b1..202d271 100644
--- a/lib/features/changelogs/view/changelog_dialog.dart
+++ b/lib/features/changelogs/view/changelog_dialog.dart
@@ -63,6 +63,7 @@ class ChangelogDialog extends StatelessWidget {
}
const _versionNumbers = {
+ "4043": "3.2.0",
"4033": "3.1.8",
"4023": "3.1.7",
"4013": "3.1.6",
diff --git a/lib/features/document_details/cubit/document_details_cubit.dart b/lib/features/document_details/cubit/document_details_cubit.dart
index 0238bd6..3c17cad 100644
--- a/lib/features/document_details/cubit/document_details_cubit.dart
+++ b/lib/features/document_details/cubit/document_details_cubit.dart
@@ -87,6 +87,47 @@ class DocumentDetailsCubit extends Cubit {
}
}
+ Future updateNote(NoteModel note) async {
+ assert(state.status == LoadingStatus.loaded);
+ final document = state.document!;
+ final updatedNotes = document.notes.map((e) => e.id == note.id ? note : e);
+ try {
+ final updatedDocument = await _api.update(
+ state.document!.copyWith(
+ notes: updatedNotes,
+ ),
+ );
+ _notifier.notifyUpdated(updatedDocument);
+ } on PaperlessApiException catch (e) {
+ addError(
+ TransientPaperlessApiError(
+ code: e.code,
+ details: e.details,
+ ),
+ );
+ }
+ }
+
+ Future deleteNote(NoteModel note) async {
+ assert(state.status == LoadingStatus.loaded,
+ "Document data has to be loaded before calling this method.");
+ assert(note.id != null, "Note id cannot be null.");
+ try {
+ final updatedDocument = await _api.deleteNote(
+ state.document!,
+ note.id!,
+ );
+ _notifier.notifyUpdated(updatedDocument);
+ } on PaperlessApiException catch (e) {
+ addError(
+ TransientPaperlessApiError(
+ code: e.code,
+ details: e.details,
+ ),
+ );
+ }
+ }
+
Future assignAsn(
DocumentModel document, {
int? asn,
@@ -270,4 +311,17 @@ class DocumentDetailsCubit extends Cubit {
_notifier.removeListener(this);
await super.close();
}
+
+ Future addNote(String text) async {
+ assert(state.status == LoadingStatus.loaded);
+ try {
+ final updatedDocument = await _api.addNote(
+ document: state.document!,
+ text: text,
+ );
+ _notifier.notifyUpdated(updatedDocument);
+ } on PaperlessApiException catch (err) {
+ addError(TransientPaperlessApiError(code: err.code));
+ }
+ }
}
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 30ea68c..d0f7301 100644
--- a/lib/features/document_details/view/pages/document_details_page.dart
+++ b/lib/features/document_details/view/pages/document_details_page.dart
@@ -15,6 +15,7 @@ import 'package:paperless_mobile/features/document_details/cubit/document_detail
import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
+import 'package:paperless_mobile/features/document_details/view/widgets/document_notes_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
@@ -67,7 +68,7 @@ class _DocumentDetailsPageState extends State {
debugPrint(disableAnimations.toString());
final hasMultiUserSupport =
context.watch().hasMultiUserSupport;
- final tabLength = 4 + (hasMultiUserSupport ? 1 : 0);
+ final tabLength = 5 + (hasMultiUserSupport ? 1 : 0);
return AnnotatedRegion(
value: buildOverlayStyle(
Theme.of(context),
@@ -160,6 +161,7 @@ class _DocumentDetailsPageState extends State {
bottom: ColoredTabBar(
tabBar: TabBar(
isScrollable: true,
+ tabAlignment: TabAlignment.start,
tabs: [
Tab(
child: Text(
@@ -201,10 +203,34 @@ class _DocumentDetailsPageState extends State {
),
),
),
+ Tab(
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ S.of(context)!.notes(0),
+ style: TextStyle(
+ color: Theme.of(context)
+ .colorScheme
+ .onPrimaryContainer,
+ ),
+ ),
+ if ((state.document?.notes.length ?? 0) >
+ 0)
+ Card(
+ child: Text(state
+ .document!.notes.length
+ .toString())
+ .paddedSymmetrically(
+ horizontal: 8, vertical: 2),
+ ),
+ ],
+ ),
+ ),
if (hasMultiUserSupport)
Tab(
child: Text(
- "Permissions",
+ S.of(context)!.permissions,
style: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -229,67 +255,103 @@ class _DocumentDetailsPageState extends State {
context.read(),
documentId: widget.id,
),
- child: Padding(
- padding: const EdgeInsets.symmetric(
- vertical: 16,
- horizontal: 16,
- ),
- child: TabBarView(
- children: [
- CustomScrollView(
- slivers: [
- SliverOverlapInjector(
- handle: NestedScrollView
- .sliverOverlapAbsorberHandleFor(context),
- ),
- switch (state.status) {
- LoadingStatus.loaded =>
- DocumentOverviewWidget(
- document: state.document!,
- itemSpacing: _itemSpacing,
- queryString:
- widget.titleAndContentQueryString,
- ),
- LoadingStatus.error => _buildErrorState(),
- _ => _buildLoadingState(),
- },
- ],
- ),
- CustomScrollView(
- slivers: [
- SliverOverlapInjector(
- handle: NestedScrollView
- .sliverOverlapAbsorberHandleFor(context),
- ),
- switch (state.status) {
- LoadingStatus.loaded => DocumentContentWidget(
- document: state.document!,
- queryString:
- widget.titleAndContentQueryString,
- ),
- LoadingStatus.error => _buildErrorState(),
- _ => _buildLoadingState(),
- }
- ],
- ),
- CustomScrollView(
- slivers: [
- SliverOverlapInjector(
- handle: NestedScrollView
- .sliverOverlapAbsorberHandleFor(context),
- ),
- switch (state.status) {
- LoadingStatus.loaded =>
- DocumentMetaDataWidget(
- document: state.document!,
- itemSpacing: _itemSpacing,
- metaData: state.metaData!,
- ),
- LoadingStatus.error => _buildErrorState(),
- _ => _buildLoadingState(),
- },
- ],
- ),
+ child: TabBarView(
+ children: [
+ CustomScrollView(
+ slivers: [
+ SliverOverlapInjector(
+ handle: NestedScrollView
+ .sliverOverlapAbsorberHandleFor(context),
+ ),
+ switch (state.status) {
+ LoadingStatus.loaded => DocumentOverviewWidget(
+ document: state.document!,
+ itemSpacing: _itemSpacing,
+ queryString:
+ widget.titleAndContentQueryString,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ LoadingStatus.error => _buildErrorState(),
+ _ => _buildLoadingState(),
+ },
+ ],
+ ),
+ CustomScrollView(
+ slivers: [
+ SliverOverlapInjector(
+ handle: NestedScrollView
+ .sliverOverlapAbsorberHandleFor(context),
+ ),
+ switch (state.status) {
+ LoadingStatus.loaded => DocumentContentWidget(
+ document: state.document!,
+ queryString:
+ widget.titleAndContentQueryString,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ LoadingStatus.error => _buildErrorState(),
+ _ => _buildLoadingState(),
+ }
+ ],
+ ),
+ CustomScrollView(
+ slivers: [
+ SliverOverlapInjector(
+ handle: NestedScrollView
+ .sliverOverlapAbsorberHandleFor(context),
+ ),
+ switch (state.status) {
+ LoadingStatus.loaded => DocumentMetaDataWidget(
+ document: state.document!,
+ itemSpacing: _itemSpacing,
+ metaData: state.metaData!,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ LoadingStatus.error => _buildErrorState(),
+ _ => _buildLoadingState(),
+ },
+ ],
+ ),
+ CustomScrollView(
+ controller: _pagingScrollController,
+ slivers: [
+ SliverOverlapInjector(
+ handle: NestedScrollView
+ .sliverOverlapAbsorberHandleFor(context),
+ ),
+ SimilarDocumentsView(
+ pagingScrollController: _pagingScrollController,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ ],
+ ),
+ CustomScrollView(
+ slivers: [
+ SliverOverlapInjector(
+ handle: NestedScrollView
+ .sliverOverlapAbsorberHandleFor(context),
+ ),
+ switch (state.status) {
+ LoadingStatus.loaded => DocumentNotesWidget(
+ document: state.document!,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ LoadingStatus.error => _buildErrorState(),
+ _ => _buildLoadingState(),
+ },
+ ],
+ ),
+ if (hasMultiUserSupport)
CustomScrollView(
controller: _pagingScrollController,
slivers: [
@@ -297,33 +359,27 @@ class _DocumentDetailsPageState extends State {
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
),
- SimilarDocumentsView(
- pagingScrollController:
- _pagingScrollController,
- ),
+ switch (state.status) {
+ LoadingStatus.loaded =>
+ DocumentPermissionsWidget(
+ document: state.document!,
+ ).paddedSymmetrically(
+ vertical: 16,
+ sliver: true,
+ ),
+ LoadingStatus.error => _buildErrorState(),
+ _ => _buildLoadingState(),
+ }
],
),
- if (hasMultiUserSupport)
- CustomScrollView(
- controller: _pagingScrollController,
- slivers: [
- SliverOverlapInjector(
- handle: NestedScrollView
- .sliverOverlapAbsorberHandleFor(
- context),
- ),
- switch (state.status) {
- LoadingStatus.loaded =>
- DocumentPermissionsWidget(
- document: state.document!,
- ),
- LoadingStatus.error => _buildErrorState(),
- _ => _buildLoadingState(),
- }
- ],
+ ]
+ .map(
+ (child) => Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: child,
),
- ],
- ),
+ )
+ .toList(),
),
);
},
diff --git a/lib/features/document_details/view/widgets/document_meta_data_widget.dart b/lib/features/document_details/view/widgets/document_meta_data_widget.dart
index 02d99fc..d11f402 100644
--- a/lib/features/document_details/view/widgets/document_meta_data_widget.dart
+++ b/lib/features/document_details/view/widgets/document_meta_data_widget.dart
@@ -25,54 +25,51 @@ class DocumentMetaDataWidget extends StatelessWidget {
Widget build(BuildContext context) {
final currentUser = context.watch().paperlessUser;
- return SliverList(
- delegate: SliverChildListDelegate(
- [
- if (currentUser.canEditDocuments)
- ArchiveSerialNumberField(
- document: document,
- ).paddedOnly(bottom: itemSpacing),
- DetailsItem.text(
- DateFormat.yMMMMd(Localizations.localeOf(context).toString())
- .format(document.modified),
- context: context,
- label: S.of(context)!.modifiedAt,
+ return SliverList.list(
+ children: [
+ if (currentUser.canEditDocuments)
+ ArchiveSerialNumberField(
+ document: document,
).paddedOnly(bottom: itemSpacing),
+ DetailsItem.text(
+ DateFormat.yMMMMd(Localizations.localeOf(context).toString())
+ .format(document.modified),
+ context: context,
+ label: S.of(context)!.modifiedAt,
+ ).paddedOnly(bottom: itemSpacing),
+ DetailsItem.text(
+ DateFormat.yMMMMd(Localizations.localeOf(context).toString())
+ .format(document.added),
+ context: context,
+ label: S.of(context)!.addedAt,
+ ).paddedOnly(bottom: itemSpacing),
+ DetailsItem.text(
+ metaData.mediaFilename,
+ context: context,
+ label: S.of(context)!.mediaFilename,
+ ).paddedOnly(bottom: itemSpacing),
+ if (document.originalFileName != null)
DetailsItem.text(
- DateFormat.yMMMMd(Localizations.localeOf(context).toString())
- .format(document.added),
- context: context,
- label: S.of(context)!.addedAt,
- ).paddedOnly(bottom: itemSpacing),
- DetailsItem.text(
- metaData.mediaFilename,
- context: context,
- label: S.of(context)!.mediaFilename,
- ).paddedOnly(bottom: itemSpacing),
- if (document.originalFileName != null)
- DetailsItem.text(
- document.originalFileName!,
- context: context,
- label: S.of(context)!.originalMD5Checksum,
- ).paddedOnly(bottom: itemSpacing),
- DetailsItem.text(
- metaData.originalChecksum,
+ document.originalFileName!,
context: context,
label: S.of(context)!.originalMD5Checksum,
).paddedOnly(bottom: itemSpacing),
- DetailsItem.text(
- formatBytes(metaData.originalSize, 2),
- context: context,
- label: S.of(context)!.originalFileSize,
- ).paddedOnly(bottom: itemSpacing),
- DetailsItem.text(
- metaData.originalMimeType,
- context: context,
- label: S.of(context)!.originalMIMEType,
- ).paddedOnly(bottom: itemSpacing),
-
- ],
- ),
+ DetailsItem.text(
+ metaData.originalChecksum,
+ context: context,
+ label: S.of(context)!.originalMD5Checksum,
+ ).paddedOnly(bottom: itemSpacing),
+ DetailsItem.text(
+ formatBytes(metaData.originalSize, 2),
+ context: context,
+ label: S.of(context)!.originalFileSize,
+ ).paddedOnly(bottom: itemSpacing),
+ DetailsItem.text(
+ metaData.originalMimeType,
+ context: context,
+ label: S.of(context)!.originalMIMEType,
+ ).paddedOnly(bottom: itemSpacing),
+ ],
);
}
}
diff --git a/lib/features/document_details/view/widgets/document_notes_widget.dart b/lib/features/document_details/view/widgets/document_notes_widget.dart
new file mode 100644
index 0000000..3fc50f3
--- /dev/null
+++ b/lib/features/document_details/view/widgets/document_notes_widget.dart
@@ -0,0 +1,179 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:intl/intl.dart';
+import 'package:paperless_api/paperless_api.dart';
+import 'package:paperless_mobile/core/database/hive/hive_config.dart';
+import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
+import 'package:paperless_mobile/core/widgets/hint_card.dart';
+import 'package:paperless_mobile/core/widgets/hint_state_builder.dart';
+import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
+import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
+import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
+import 'package:paperless_mobile/helpers/message_helpers.dart';
+import 'package:markdown/markdown.dart' show markdownToHtml;
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+
+class DocumentNotesWidget extends StatefulWidget {
+ final DocumentModel document;
+ const DocumentNotesWidget({super.key, required this.document});
+
+ @override
+ State createState() => _DocumentNotesWidgetState();
+}
+
+class _DocumentNotesWidgetState extends State {
+ final _noteContentController = TextEditingController();
+ final _formKey = GlobalKey();
+ bool _isNoteSubmitting = false;
+ @override
+ Widget build(BuildContext context) {
+ const hintKey = "hideMarkdownSyntaxHint";
+ return SliverMainAxisGroup(
+ slivers: [
+ SliverPadding(
+ padding: const EdgeInsets.only(bottom: 16),
+ sliver: SliverToBoxAdapter(
+ child: HintStateBuilder(
+ listenKey: hintKey,
+ builder: (context, box) {
+ return HintCard(
+ hintText: S.of(context)!.notesMarkdownSyntaxSupportHint,
+ show: !box.get(hintKey, defaultValue: false)!,
+ onHintAcknowledged: () {
+ box.put(hintKey, true);
+ },
+ );
+ },
+ ),
+ ),
+ ),
+ SliverToBoxAdapter(
+ child: Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ TextFormField(
+ controller: _noteContentController,
+ maxLines: null,
+ validator: (value) {
+ if (value?.trim().isEmpty ?? true) {
+ return S.of(context)!.thisFieldIsRequired;
+ }
+ return null;
+ },
+ textInputAction: TextInputAction.newline,
+ decoration: InputDecoration(
+ labelText: S.of(context)!.newNote,
+ suffixIcon: IconButton(
+ icon: const Icon(Icons.clear),
+ onPressed: () {
+ _noteContentController.clear();
+ },
+ ),
+ ),
+ ).paddedOnly(bottom: 8),
+ Align(
+ alignment: Alignment.centerRight,
+ child: ElevatedButton.icon(
+ icon: _isNoteSubmitting
+ ? const SizedBox.square(
+ dimension: 20,
+ child: Center(
+ child: CircularProgressIndicator(
+ strokeWidth: 3,
+ ),
+ ),
+ )
+ : const Icon(Icons.note_add_outlined),
+ label: Text(S.of(context)!.addNote),
+ onPressed: () async {
+ _formKey.currentState?.save();
+ FocusScope.of(context).unfocus();
+
+ if (_formKey.currentState?.validate() ?? false) {
+ setState(() {
+ _isNoteSubmitting = true;
+ });
+ try {
+ await context
+ .read()
+ .addNote(_noteContentController.text.trim());
+ _noteContentController.clear();
+ } catch (error) {
+ showGenericError(context, error);
+ } finally {
+ setState(() {
+ _isNoteSubmitting = false;
+ });
+ }
+ }
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SliverToBoxAdapter(
+ child: SizedBox(height: 16),
+ ),
+ SliverList.separated(
+ separatorBuilder: (context, index) => const SizedBox(height: 16),
+ itemBuilder: (context, index) {
+ final note = widget.document.notes.elementAt(index);
+ return Card(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Html(
+ data: markdownToHtml(note.note!),
+ onLinkTap: (url, attributes, element) async {
+ if (url?.isEmpty ?? true) {
+ return;
+ }
+ if (await canLaunchUrlString(url!)) {
+ launchUrlString(url);
+ }
+ },
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ if (note.created != null)
+ Text(
+ DateFormat.yMMMd(
+ Localizations.localeOf(context).toString())
+ .addPattern('\u2014')
+ .add_jm()
+ .format(note.created!),
+ style:
+ Theme.of(context).textTheme.labelMedium?.copyWith(
+ color: Theme.of(context)
+ .colorScheme
+ .onSurface
+ .withOpacity(.5),
+ ),
+ ),
+ IconButton(
+ tooltip: S.of(context)!.delete,
+ icon: const Icon(Icons.delete),
+ onPressed: () {
+ context.read().deleteNote(note);
+ },
+ ),
+ ],
+ ),
+ ],
+ ).padded(16),
+ );
+ },
+ itemCount: widget.document.notes.length,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/features/document_edit/view/document_edit_page.dart b/lib/features/document_edit/view/document_edit_page.dart
index eaa16a9..8631d72 100644
--- a/lib/features/document_edit/view/document_edit_page.dart
+++ b/lib/features/document_edit/view/document_edit_page.dart
@@ -424,7 +424,7 @@ class _DocumentEditPageState extends State
initialValue: initialCreatedAtDate,
labelText: S.of(context)!.createdAt,
firstDate: DateTime(1970, 1, 1),
- lastDate: DateTime.now(),
+ lastDate: DateTime(2100, 1, 1),
locale: Localizations.localeOf(context),
prefixIcon: Icon(Icons.calendar_today),
),
diff --git a/lib/features/document_scan/cubit/document_scanner_cubit.dart b/lib/features/document_scan/cubit/document_scanner_cubit.dart
index 7cf0c52..dce5108 100644
--- a/lib/features/document_scan/cubit/document_scanner_cubit.dart
+++ b/lib/features/document_scan/cubit/document_scanner_cubit.dart
@@ -54,7 +54,9 @@ class DocumentScannerCubit extends Cubit {
Future removeScan(File file) async {
try {
- await file.delete();
+ if (await file.exists()) {
+ await file.delete();
+ }
} catch (error, stackTrace) {
throw InfoMessageException(
code: ErrorCode.scanRemoveFailed,
diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart
index 2bf183f..6d63430 100644
--- a/lib/features/document_scan/view/scanner_page.dart
+++ b/lib/features/document_scan/view/scanner_page.dart
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/core/bloc/loading_status.dart';
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/global/constants.dart';
+import 'package:paperless_mobile/core/model/info_message_exception.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
@@ -326,6 +327,8 @@ class _ScannerPageState extends State
.removeScan(scans[index]);
} on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
+ } on InfoMessageException catch (error, stackTrace) {
+ showInfoMessage(context, error, stackTrace);
}
},
index: index,
diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart
index 4ab0df6..d605122 100644
--- a/lib/features/document_upload/view/document_upload_preparation_page.dart
+++ b/lib/features/document_upload/view/document_upload_preparation_page.dart
@@ -222,7 +222,7 @@ class _DocumentUploadPreparationPageState
FormBuilderLocalizedDatePicker(
name: DocumentModel.createdKey,
firstDate: DateTime(1970, 1, 1),
- lastDate: DateTime.now(),
+ lastDate: DateTime(2100, 1, 1),
locale: Localizations.localeOf(context),
labelText: S.of(context)!.createdAt + " *",
allowUnset: true,
diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart
index c1cc561..7b86f63 100644
--- a/lib/features/documents/view/pages/documents_page.dart
+++ b/lib/features/documents/view/pages/documents_page.dart
@@ -21,6 +21,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/docum
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
+import 'package:paperless_mobile/features/logging/data/logger.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -308,8 +309,18 @@ class _DocumentsPageState extends State {
// Listen for scroll notifications to load new data.
// Scroll controller does not work here due to nestedscrollview limitations.
final offset = notification.metrics.pixels;
- if (offset > 128 && _savedViewsExpansionController.isExpanded) {
- _savedViewsExpansionController.collapse();
+ try {
+ if (offset > 128 && _savedViewsExpansionController.isExpanded) {
+ _savedViewsExpansionController.collapse();
+ }
+ // Workaround for https://github.com/astubenbord/paperless-mobile/issues/341 probably caused by https://github.com/flutter/flutter/issues/138153
+ } on TypeError catch (error) {
+ logger.fw(
+ "An exception was thrown, but this message can probably be ignored. See issue #341 for more details.",
+ error: error,
+ className: runtimeType.toString(),
+ methodName: "_buildDocumentsTab",
+ );
}
final max = notification.metrics.maxScrollExtent;
diff --git a/lib/features/login/cubit/authentication_cubit.dart b/lib/features/login/cubit/authentication_cubit.dart
index 121e779..1c1eac6 100644
--- a/lib/features/login/cubit/authentication_cubit.dart
+++ b/lib/features/login/cubit/authentication_cubit.dart
@@ -4,6 +4,8 @@ import 'package:flutter/widgets.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
+import 'package:paperless_mobile/constants.dart';
+import 'package:paperless_mobile/core/bloc/transient_error.dart';
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
@@ -13,6 +15,7 @@ import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
+import 'package:paperless_mobile/core/security/session_manager_impl.dart';
import 'package:paperless_mobile/features/logging/data/logger.dart';
import 'package:paperless_mobile/features/logging/utils/redaction_utils.dart';
import 'package:paperless_mobile/core/model/info_message_exception.dart';
@@ -83,7 +86,7 @@ class AuthenticationCubit extends Cubit {
AuthenticatingStage.persistingLocalUserData));
},
);
- } catch (e) {
+ } on PaperlessApiException catch (exception, stackTrace) {
emit(
AuthenticationErrorState(
serverUrl: serverUrl,
@@ -207,8 +210,8 @@ class AuthenticationCubit extends Cubit {
methodName: 'switchAccount',
);
- final sessionManager = SessionManager([
- LanguageHeaderInterceptor(locale),
+ final SessionManager sessionManager = SessionManagerImpl([
+ LanguageHeaderInterceptor(() => locale),
]);
await _addUser(
localUserId,
@@ -462,14 +465,12 @@ class AuthenticationCubit extends Cubit {
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
+ await onPerformLogin?.call();
logger.fd(
"Fetching bearer token from the server...",
className: runtimeType.toString(),
methodName: '_addUser',
);
-
- await onPerformLogin?.call();
-
final token = await authApi.login(
username: credentials.username!,
password: credentials.password!,
@@ -486,7 +487,6 @@ class AuthenticationCubit extends Cubit {
clientCertificate: clientCert,
authToken: token,
);
-
final userAccountBox =
Hive.box(HiveBoxes.localUserAccount);
final userStateBox =
@@ -586,12 +586,14 @@ class AuthenticationCubit extends Cubit {
clientCertificate: clientCert,
),
);
+
logger.fd(
"User credentials successfully saved.",
className: runtimeType.toString(),
methodName: '_addUser',
);
});
+
final hostsBox = Hive.box(HiveBoxes.hosts);
if (!hostsBox.values.contains(serverUrl)) {
await hostsBox.add(serverUrl);
@@ -618,12 +620,19 @@ class AuthenticationCubit extends Cubit {
try {
final response = await dio.get(
"/api/",
- options: Options(
- sendTimeout: timeout,
- ),
+ options: Options(sendTimeout: timeout),
);
- final apiVersion =
+ int apiVersion =
int.parse(response.headers.value('x-api-version') ?? "3");
+ if (apiVersion > latestSupportedApiVersion) {
+ logger.fw(
+ "The server is running a newer API version ($apiVersion) than the app supports (v$latestSupportedApiVersion), falling back to latest supported version (v$latestSupportedApiVersion). "
+ "Warning: This might lead to unexpected behavior!",
+ className: runtimeType.toString(),
+ methodName: '_getApiVersion',
+ );
+ apiVersion = latestSupportedApiVersion;
+ }
logger.fd(
"Successfully retrieved API version ($apiVersion).",
className: runtimeType.toString(),
diff --git a/lib/features/login/view/add_account_page.dart b/lib/features/login/view/add_account_page.dart
index fcc9aab..7bb82c6 100644
--- a/lib/features/login/view/add_account_page.dart
+++ b/lib/features/login/view/add_account_page.dart
@@ -1,6 +1,5 @@
import 'dart:async';
-import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
@@ -17,7 +16,6 @@ import 'package:paperless_mobile/features/login/model/client_certificate_form_mo
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
-import 'package:paperless_mobile/features/login/view/widgets/form_fields/login_settings_page.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
import 'package:paperless_mobile/generated/assets.gen.dart';
@@ -44,6 +42,7 @@ class AddAccountPage extends StatefulWidget {
final bool showLocalAccounts;
final Widget? bottomLeftButton;
+
const AddAccountPage({
Key? key,
required this.onSubmit,
diff --git a/lib/features/login/view/widgets/form_fields/server_address_form_field.dart b/lib/features/login/view/widgets/form_fields/server_address_form_field.dart
index 171ab27..ac2d476 100644
--- a/lib/features/login/view/widgets/form_fields/server_address_form_field.dart
+++ b/lib/features/login/view/widgets/form_fields/server_address_form_field.dart
@@ -5,6 +5,7 @@ import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
+import 'package:paperless_mobile/keys.dart';
class ServerAddressFormField extends StatefulWidget {
static const String fkServerAddress = "serverAddress";
@@ -59,7 +60,7 @@ class _ServerAddressFormFieldState extends State
maxWidth: MediaQuery.sizeOf(context).width - 40,
);
},
- key: const ValueKey('login-server-address'),
+ key: TestKeys.login.serverAddressFormField,
optionsBuilder: (textEditingValue) {
return Hive.box(HiveBoxes.hosts)
.values
diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart
index 2b43ca8..872a30e 100644
--- a/lib/features/settings/view/settings_page.dart
+++ b/lib/features/settings/view/settings_page.dart
@@ -1,3 +1,5 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/settings/view/widgets/app_logs_tile.dart';
@@ -15,6 +17,7 @@ import 'package:paperless_mobile/features/settings/view/widgets/theme_mode_setti
import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher_string.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@@ -80,15 +83,49 @@ class SettingsPage extends StatelessWidget {
);
}
final serverData = snapshot.data!;
- return Text(
- S.of(context)!.paperlessServerVersion +
- ' ' +
- serverData.version.toString() +
- ' (API v${serverData.apiVersion})',
- style: Theme.of(context).textTheme.labelSmall?.copyWith(
- color: Theme.of(context).colorScheme.secondary,
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ S.of(context)!.paperlessServerVersion +
+ ' ' +
+ serverData.version.toString() +
+ ' (API v${serverData.apiVersion})',
+ style: Theme.of(context).textTheme.labelSmall?.copyWith(
+ color: Theme.of(context).colorScheme.secondary,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ if (serverData.isUpdateAvailable) ...[
+ SizedBox(height: 8),
+ Text.rich(
+ TextSpan(
+ style: Theme.of(context).textTheme.labelSmall!,
+ text: '${S.of(context)!.newerVersionAvailable} ',
+ children: [
+ TextSpan(
+ text: serverData.latestVersion,
+ style: Theme.of(context)
+ .textTheme
+ .labelSmall!
+ .copyWith(
+ decoration: TextDecoration.underline,
+ color: CupertinoColors.link,
+ decorationColor: CupertinoColors.link,
+ ),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrlString(
+ "https://github.com/paperless-ngx/paperless-ngx/releases/tag/${serverData.latestVersion}",
+ );
+ },
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
),
- textAlign: TextAlign.center,
+ ]
+ ],
);
},
),
diff --git a/lib/features/settings/view/widgets/language_selection_setting.dart b/lib/features/settings/view/widgets/language_selection_setting.dart
index cb18576..b742e40 100644
--- a/lib/features/settings/view/widgets/language_selection_setting.dart
+++ b/lib/features/settings/view/widgets/language_selection_setting.dart
@@ -23,6 +23,7 @@ class _LanguageSelectionSettingState extends State {
'pl': LanguageOption('Polska', true),
'ca': LanguageOption('Català', true),
'ru': LanguageOption('Русский', true),
+ 'it': LanguageOption('Italiano', true),
};
@override
diff --git a/lib/keys.dart b/lib/keys.dart
new file mode 100644
index 0000000..43e3197
--- /dev/null
+++ b/lib/keys.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/widgets.dart';
+
+class TestKeys {
+ TestKeys._();
+
+ static final login = _LoginTestKeys();
+}
+
+class _LoginTestKeys {
+ final serverAddressFormField = const Key('login-server-address');
+ final continueButton = const Key('login-continue-button');
+ final usernameFormField = const Key('login-username');
+ final passwordFormField = const Key('login-password');
+ final loginButton = const Key('login-login-button');
+ final clientCertificateFormField = const Key('login-client-certificate');
+ final clientCertificatePassphraseFormField =
+ const Key('login-client-certificate-passphrase');
+ final loggingInScreen = const Key('login-logging-in-screen');
+}
diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb
index 1915311..878c73c 100644
--- a/lib/l10n/intl_ca.arb
+++ b/lib/l10n/intl_ca.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Versió {versionCode}"
+ "version": "Versió {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Nota} other{Notes}}",
+ "addNote": "Afegir Nota",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb
index 32c55a9..2a2634e 100644
--- a/lib/l10n/intl_cs.arb
+++ b/lib/l10n/intl_cs.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb
index 6118dd3..8453eb2 100644
--- a/lib/l10n/intl_de.arb
+++ b/lib/l10n/intl_de.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notizen} one{Notiz} other{Notizen}}",
+ "addNote": "Notiz hinzufügen",
+ "newerVersionAvailable": "Neuere Version verfügbar:",
+ "dateOutOfRange": "Das Datum muss zwischen {firstDate} und {lastDate} liegen.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Berechtigungen",
+ "newNote": "Neue Notiz",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile unterstützt Markdown-Syntax zur Darstellung und Formatierung von Notizen. Probiere es aus!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 84674b1..4f8f10b 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb
index f05b474..b34def0 100644
--- a/lib/l10n/intl_es.arb
+++ b/lib/l10n/intl_es.arb
@@ -1020,9 +1020,29 @@
},
"misc": "Otros",
"loggingOut": "Cerrando sesión...",
- "testingConnection": "Testing connection...",
+ "testingConnection": "Probando conexión...",
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notas} one{Nota} other{Notas}}",
+ "addNote": "Añadir nota",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb
index 471430d..d73fa3c 100644
--- a/lib/l10n/intl_fr.arb
+++ b/lib/l10n/intl_fr.arb
@@ -703,7 +703,7 @@
"@confirmAction": {
"description": "Typically used as a title to confirm a previously selected action"
},
- "areYouSureYouWantToContinue": "Etes-vous sûr(e) de vouloir continuer?",
+ "areYouSureYouWantToContinue": "Êtes-vous sûr(e) de vouloir continuer ?",
"bulkEditTagsAddMessage": "{count, plural, one{Cette opération va ajouter les balises {tags} au document sélectionné} other{Cette opération va ajouter les balises {tags} à {count} documents sélectionnés!}}",
"@bulkEditTagsAddMessage": {
"description": "Message of the confirmation dialog when bulk adding tags."
@@ -717,7 +717,7 @@
"description": "Message of the confirmation dialog when both adding and removing tags."
},
"bulkEditCorrespondentAssignMessage": "{count, plural, one{Cette opération assignera le correspondant {correspondent} au document sélectionné} other{Cette opération va assigner le correspondant {correspondent} à {count} documents sélectionnés!}}",
- "bulkEditDocumentTypeAssignMessage": "{count, plural, one{Cette opération assignera le type de document {docType} au document sélectionné.} other{Cette opération va assigner le documentType {docType} à {count} documents sélectionnés.}}",
+ "bulkEditDocumentTypeAssignMessage": "{count, plural, one{Cette opération assignera le type de document {docType} au document sélectionné.} other{Cette opération va assigner le type de document {docType} à {count} documents sélectionnés.}}",
"bulkEditStoragePathAssignMessage": "{count, plural, one{Cette opération assignera le chemin de stockage {path} au document sélectionné.} other{Cette opération va assigner le chemin de stockage {path} à {count} documents sélectionnés.}}",
"bulkEditCorrespondentRemoveMessage": "{count, plural, one{Cette opération va supprimer le correspondant du document sélectionné.} other{Cette opération va supprimer le correspondant de {count} documents sélectionnés.}}",
"bulkEditDocumentTypeRemoveMessage": "{count, plural, one{Cette opération va supprimer le type de document du document sélectionné.} other{Cette opération va supprimer le type de document de {count} documents sélectionnés.}}",
@@ -772,7 +772,7 @@
"@defaultDownloadFileType": {
"description": "Label indicating the default filetype to download (one of archived, original and always ask)"
},
- "defaultShareFileType": "Type de fichier par défaut de partage",
+ "defaultShareFileType": "Type de fichier par défaut pour le partage",
"@defaultShareFileType": {
"description": "Label indicating the default filetype to share (one of archived, original and always ask)"
},
@@ -861,7 +861,7 @@
"@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": "Vous n'avez pas les permissions nécessaires pour faire cette action.",
"@missingPermissions": {
"description": "Message shown in a snackbar when a user without the reequired permissions performs an action."
},
@@ -873,156 +873,176 @@
"@donate": {
"description": "Label of the in-app donate button"
},
- "donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
+ "donationDialogContent": "Merci d'avoir envisagé de soutenir cette application ! En raison des politiques de paiement de Google et d'Apple, aucun lien menant aux dons ne peut être affiché dans l'application. Même un lien vers la page du dépôt du projet ne semble pas autorisé dans ce contexte. Par conséquent, jetez peut-être un coup d'oeil à la section « Donations » dans le README du projet. Votre soutien est très apprécié et maintient en vie le développement de cette application. Merci !",
"@donationDialogContent": {
"description": "Text displayed in the donation dialog"
},
- "noDocumentsFound": "No documents found.",
+ "noDocumentsFound": "Aucun document trouvé.",
"@noDocumentsFound": {
"description": "Message shown when no documents were found."
},
- "couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
+ "couldNotDeleteCorrespondent": "Impossible de supprimer le correspondant, veuillez réessayer.",
"@couldNotDeleteCorrespondent": {
"description": "Message shown in snackbar when a correspondent could not be deleted."
},
- "couldNotDeleteDocumentType": "Could not delete document type, please try again.",
+ "couldNotDeleteDocumentType": "Impossible de supprimer ce type de document, veuillez réessayer.",
"@couldNotDeleteDocumentType": {
"description": "Message shown when a document type could not be deleted"
},
- "couldNotDeleteTag": "Could not delete tag, please try again.",
+ "couldNotDeleteTag": "Impossible de supprimer l'étiquette, veuillez réessayer.",
"@couldNotDeleteTag": {
"description": "Message shown when a tag could not be deleted"
},
- "couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
+ "couldNotDeleteStoragePath": "Impossible de supprimer le chemin de stockage, veuillez réessayer.",
"@couldNotDeleteStoragePath": {
"description": "Message shown when a storage path could not be deleted"
},
- "couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
+ "couldNotUpdateCorrespondent": "Impossible de mettre à jour le correspondant, veuillez réessayer.",
"@couldNotUpdateCorrespondent": {
"description": "Message shown when a correspondent could not be updated"
},
- "couldNotUpdateDocumentType": "Could not update document type, please try again.",
+ "couldNotUpdateDocumentType": "Impossible de mettre à jour le type de document, veuillez réessayer.",
"@couldNotUpdateDocumentType": {
"description": "Message shown when a document type could not be updated"
},
- "couldNotUpdateTag": "Could not update tag, please try again.",
+ "couldNotUpdateTag": "Impossible de mettre à jour l'étiquette, veuillez réessayer.",
"@couldNotUpdateTag": {
"description": "Message shown when a tag could not be updated"
},
- "couldNotLoadServerInformation": "Could not load server information.",
+ "couldNotLoadServerInformation": "Impossible de charger les informations du serveur.",
"@couldNotLoadServerInformation": {
"description": "Message shown when the server information could not be loaded"
},
- "couldNotLoadStatistics": "Could not load server statistics.",
+ "couldNotLoadStatistics": "Impossible de charger les statistiques du serveur.",
"@couldNotLoadStatistics": {
"description": "Message shown when the server statistics could not be loaded"
},
- "couldNotLoadUISettings": "Could not load UI settings.",
+ "couldNotLoadUISettings": "Impossible de charger les paramètres de l'interface.",
"@couldNotLoadUISettings": {
"description": "Message shown when the UI settings could not be loaded"
},
- "couldNotLoadTasks": "Could not load tasks.",
+ "couldNotLoadTasks": "Impossible de charger les tâches.",
"@couldNotLoadTasks": {
"description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
},
- "userNotFound": "User could not be found.",
+ "userNotFound": "L'utilisateur ne peut pas être trouvé.",
"@userNotFound": {
"description": "Message shown when the specified user (e.g. by id) could not be found"
},
- "couldNotUpdateSavedView": "Could not update saved view, please try again.",
+ "couldNotUpdateSavedView": "Impossible de mettre à jour la vue, veuillez réessayer.",
"@couldNotUpdateSavedView": {
"description": "Message shown when a saved view could not be updated"
},
- "couldNotUpdateStoragePath": "Could not update storage path, please try again.",
- "savedViewSuccessfullyUpdated": "Saved view successfully updated.",
+ "couldNotUpdateStoragePath": "Impossible de mettre à jour le chemin de stockage, veuillez réessayer.",
+ "savedViewSuccessfullyUpdated": "Vue enregistrée mise à jour avec succès.",
"@savedViewSuccessfullyUpdated": {
"description": "Message shown when a saved view was successfully updated."
},
- "discardChanges": "Discard changes?",
+ "discardChanges": "Annuler les modifications ?",
"@discardChanges": {
"description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
},
- "savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
+ "savedViewChangedDialogContent": "Les conditions de filtre de la vue active ont changé. En réinitialisant le filtre, ces modifications seront perdues. Voulez-vous continuer ?",
"@savedViewChangedDialogContent": {
"description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
},
- "createFromCurrentFilter": "Create from current filter",
+ "createFromCurrentFilter": "Créer à partir du filtre actuel",
"@createFromCurrentFilter": {
"description": "Tooltip of the \"New saved view\" button"
},
- "home": "Home",
+ "home": "Accueil",
"@home": {
"description": "Label of the \"Home\" route"
},
- "welcomeUser": "Welcome, {name}!",
+ "welcomeUser": "Bienvenue {name} !",
"@welcomeUser": {
"description": "Top message shown on the home page"
},
- "statistics": "Statistics",
- "documentsInInbox": "Documents in inbox",
- "totalDocuments": "Total documents",
- "totalCharacters": "Total characters",
- "showAll": "Show all",
+ "statistics": "Statistiques",
+ "documentsInInbox": "Documents dans la boîte de réception",
+ "totalDocuments": "Nombre total de documents",
+ "totalCharacters": "Nombre total de caractères",
+ "showAll": "Tout afficher",
"@showAll": {
"description": "Button label shown on a saved view preview to open this view in the documents page"
},
- "userAlreadyExists": "This user already exists.",
+ "userAlreadyExists": "Cet utilisateur existe déjà.",
"@userAlreadyExists": {
"description": "Error message shown when the user tries to add an already existing account."
},
- "youDidNotSaveAnyViewsYet": "You did not save any views yet, create one and it will be shown here.",
+ "youDidNotSaveAnyViewsYet": "Vous n'avez pas encore enregistré de vues, créez en une et elle sera affichée ici.",
"@youDidNotSaveAnyViewsYet": {
"description": "Message shown when there are no saved views yet."
},
- "tryAgain": "Try again",
- "discardFile": "Discard file?",
- "discard": "Discard",
- "backToLogin": "Back to login",
- "skipEditingReceivedFiles": "Skip editing received files",
- "uploadWithoutPromptingUploadForm": "Always upload without prompting the upload form when sharing files with the app.",
- "authenticatingDots": "Authenticating...",
+ "tryAgain": "Veuillez réessayer",
+ "discardFile": "Abandonner le fichier ?",
+ "discard": "Abandonner",
+ "backToLogin": "Retour à la page de connexion",
+ "skipEditingReceivedFiles": "Passer l'édition des fichiers reçus",
+ "uploadWithoutPromptingUploadForm": "Toujours mettre en ligne sans montrer le formulaire de mise en ligne lors du partage de fichiers avec l'application.",
+ "authenticatingDots": "Authentification en cours...",
"@authenticatingDots": {
"description": "Message shown when the app is authenticating the user"
},
- "persistingUserInformation": "Persisting user information...",
- "fetchingUserInformation": "Fetching user information...",
+ "persistingUserInformation": "Sauvegarde des informations utilisateur...",
+ "fetchingUserInformation": "Récupération des informations utilisateur...",
"@fetchingUserInformation": {
"description": "Message shown when the app loads user data from the server"
},
- "restoringSession": "Restoring session...",
+ "restoringSession": "Restauration de la session...",
"@restoringSession": {
"description": "Message shown when the user opens the app and the previous user is tried to be authenticated and logged in"
},
- "documentsAssigned": "{count, plural, zero{No documents} one{1 document} other{{count} documents}}",
+ "documentsAssigned": "{count, plural, zero{Pas de document} one{1 document} other{{count} documents}}",
"@documentsAssigned": {
"description": "Text shown with a correspondent, document type etc. to indicate the number of documents this filter will maximally yield."
},
- "discardChangesWarning": "You have unsaved changes. By continuing, all changes will be lost. Do you want to discard these changes?",
+ "discardChangesWarning": "Vous avez des modifications non enregistrées. En continuant, toutes les modifications seront perdues. Voulez-vous abandonner ces modifications ?",
"@discardChangesWarning": {
"description": "Warning message shown when the user tries to close a route without saving the changes."
},
- "changelog": "Changelog",
- "noLogsFoundOn": "No logs found on {date}.",
- "logfileBottomReached": "You have reached the bottom of this logfile.",
- "appLogs": "App logs {date}",
- "saveLogsToFile": "Save logs to file",
- "copyToClipboard": "Copy to clipboard",
- "couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
- "loadingLogsFrom": "Loading logs from {date}...",
- "clearLogs": "Clear logs from {date}",
- "showPdf": "Show PDF",
+ "changelog": "Notes de version",
+ "noLogsFoundOn": "Aucun journal trouvé sur {date}.",
+ "logfileBottomReached": "Vous avez atteint le bas de ce fichier journal.",
+ "appLogs": "Journaux d'application {date}",
+ "saveLogsToFile": "Enregistrer le fichier journal",
+ "copyToClipboard": "Copier dans le presse-papier",
+ "couldNotLoadLogfileFrom": "Impossible de charger le fichier journal depuis {date}.",
+ "loadingLogsFrom": "Chargement des journaux depuis {date}...",
+ "clearLogs": "Effacer les journaux de {date}",
+ "showPdf": "Afficher le PDF",
"@showPdf": {
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
},
- "hidePdf": "Hide PDF",
+ "hidePdf": "Masquer le PDF",
"@hidePdf": {
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
},
"misc": "Sonstige",
- "loggingOut": "Logging out...",
- "testingConnection": "Testing connection...",
+ "loggingOut": "Déconnexion...",
+ "testingConnection": "Vérifier la connexion...",
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Ajouter une note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb
new file mode 100644
index 0000000..5e3605d
--- /dev/null
+++ b/lib/l10n/intl_it.arb
@@ -0,0 +1,1048 @@
+{
+ "developedBy": "Sviluppato da {name}.",
+ "@developedBy": {
+ "placeholders": {
+ "name": {}
+ }
+ },
+ "addAnotherAccount": "Aggiungi un altro account",
+ "@addAnotherAccount": {},
+ "account": "Account",
+ "@account": {},
+ "addCorrespondent": "Nuovo Corrispondente",
+ "@addCorrespondent": {
+ "description": "Title when adding a new correspondent"
+ },
+ "addDocumentType": "Nuovo Tipo Di Documento",
+ "@addDocumentType": {
+ "description": "Title when adding a new document type"
+ },
+ "addStoragePath": "Nuovo Percorso Di Archiviazione",
+ "@addStoragePath": {
+ "description": "Title when adding a new storage path"
+ },
+ "addTag": "Nuovo Tag",
+ "@addTag": {
+ "description": "Title when adding a new tag"
+ },
+ "aboutThisApp": "Info sull'app",
+ "@aboutThisApp": {
+ "description": "Label for about this app tile displayed in the drawer"
+ },
+ "loggedInAs": "Accesso effettuato come {name}",
+ "@loggedInAs": {
+ "placeholders": {
+ "name": {}
+ }
+ },
+ "disconnect": "Disconnetti",
+ "@disconnect": {
+ "description": "Logout button label"
+ },
+ "reportABug": "Segnala un Bug",
+ "@reportABug": {},
+ "settings": "Impostazioni",
+ "@settings": {},
+ "authenticateOnAppStart": "Autenticarsi all'avvio dell'app",
+ "@authenticateOnAppStart": {
+ "description": "Description of the biometric authentication settings tile"
+ },
+ "biometricAuthentication": "Autenticazione biometrica",
+ "@biometricAuthentication": {},
+ "authenticateToToggleBiometricAuthentication": "{mode, select, enable{Autenticati per abilitare l'autenticazione biometrica} disable{Autenticati per disabilitare l'autenticazione biometrica} other{}}",
+ "@authenticateToToggleBiometricAuthentication": {
+ "placeholders": {
+ "mode": {}
+ }
+ },
+ "documents": "Documenti",
+ "@documents": {},
+ "inbox": "Posta in arrivo",
+ "@inbox": {},
+ "labels": "Etichette",
+ "@labels": {},
+ "scanner": "Scansiona",
+ "@scanner": {},
+ "startTyping": "Inizia a scrivere...",
+ "@startTyping": {},
+ "doYouReallyWantToDeleteThisView": "Sei sicuro di voler cancellare questa vista?",
+ "@doYouReallyWantToDeleteThisView": {},
+ "deleteView": "Cancellare vista {name}?",
+ "@deleteView": {},
+ "addedAt": "Aggiunto il",
+ "@addedAt": {},
+ "archiveSerialNumber": "Numero seriale di archivio",
+ "@archiveSerialNumber": {},
+ "asn": "ASN",
+ "@asn": {},
+ "correspondent": "Corrispondente",
+ "@correspondent": {},
+ "createdAt": "Creato il",
+ "@createdAt": {},
+ "documentSuccessfullyDeleted": "Documento cancellato con successo.",
+ "@documentSuccessfullyDeleted": {},
+ "assignAsn": "Assegna ASN",
+ "@assignAsn": {},
+ "deleteDocumentTooltip": "Cancella",
+ "@deleteDocumentTooltip": {
+ "description": "Tooltip shown for the delete button on details page"
+ },
+ "downloadDocumentTooltip": "Scarica",
+ "@downloadDocumentTooltip": {
+ "description": "Tooltip shown for the download button on details page"
+ },
+ "editDocumentTooltip": "Modifica",
+ "@editDocumentTooltip": {
+ "description": "Tooltip shown for the edit button on details page"
+ },
+ "loadFullContent": "Carica intero contenuto",
+ "@loadFullContent": {},
+ "noAppToDisplayPDFFilesFound": "Nessuna app trovata per mostrare i file PDF!",
+ "@noAppToDisplayPDFFilesFound": {},
+ "openInSystemViewer": "Apri nel visualizzatore di sistema",
+ "@openInSystemViewer": {},
+ "couldNotOpenFilePermissionDenied": "Impossibile aprire il file: Permesso negato.",
+ "@couldNotOpenFilePermissionDenied": {},
+ "previewTooltip": "Anteprima",
+ "@previewTooltip": {
+ "description": "Tooltip shown for the preview button on details page"
+ },
+ "shareTooltip": "Condividi",
+ "@shareTooltip": {
+ "description": "Tooltip shown for the share button on details page"
+ },
+ "similarDocuments": "Documenti Simili",
+ "@similarDocuments": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "content": "Contenuto",
+ "@content": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "metaData": "Metadati",
+ "@metaData": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "overview": "Panoramica",
+ "@overview": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "documentType": "Tipo di documento",
+ "@documentType": {},
+ "archivedPdf": "Archiviato (PDF)",
+ "@archivedPdf": {
+ "description": "Option to chose when downloading a document"
+ },
+ "chooseFiletype": "Scegli tipo di file",
+ "@chooseFiletype": {},
+ "original": "Originale",
+ "@original": {
+ "description": "Option to chose when downloading a document"
+ },
+ "documentSuccessfullyDownloaded": "Documento scaricato con successo.",
+ "@documentSuccessfullyDownloaded": {},
+ "suggestions": "Suggerimenti: ",
+ "@suggestions": {},
+ "editDocument": "Modifica Documento",
+ "@editDocument": {},
+ "advanced": "Avanzate",
+ "@advanced": {},
+ "apply": "Applica",
+ "@apply": {},
+ "extended": "Esteso",
+ "@extended": {},
+ "titleAndContent": "Titolo E Contenuti",
+ "@titleAndContent": {},
+ "title": "Titolo",
+ "@title": {},
+ "reset": "Ripristina",
+ "@reset": {},
+ "filterDocuments": "Filtra Documenti",
+ "@filterDocuments": {
+ "description": "Title of the document filter"
+ },
+ "originalMD5Checksum": "Checksum MD5 Originale",
+ "@originalMD5Checksum": {},
+ "mediaFilename": "Nome File Multimediale",
+ "@mediaFilename": {},
+ "originalFileSize": "Dimensione File Originale",
+ "@originalFileSize": {},
+ "originalMIMEType": "Tipo MIME Originale",
+ "@originalMIMEType": {},
+ "modifiedAt": "Modificato il",
+ "@modifiedAt": {},
+ "preview": "Anteprima",
+ "@preview": {
+ "description": "Title of the document preview page"
+ },
+ "scanADocument": "Scansiona un documento",
+ "@scanADocument": {},
+ "noDocumentsScannedYet": "Nessun documento ancora scansionato.",
+ "@noDocumentsScannedYet": {},
+ "or": "o",
+ "@or": {
+ "description": "Used on the scanner page between both main actions when no scans have been captured."
+ },
+ "deleteAllScans": "Elimina tutte le scansioni",
+ "@deleteAllScans": {},
+ "uploadADocumentFromThisDevice": "Carica un documento da questo dispositivo",
+ "@uploadADocumentFromThisDevice": {
+ "description": "Button label on scanner page"
+ },
+ "noMatchesFound": "Nessuna corrispondenza trovata.",
+ "@noMatchesFound": {
+ "description": "Displayed when no documents were found in the document search."
+ },
+ "removeFromSearchHistory": "Rimuovere dalla cronologia di ricerca?",
+ "@removeFromSearchHistory": {},
+ "results": "Risultati",
+ "@results": {
+ "description": "Label displayed above search results in document search."
+ },
+ "searchDocuments": "Cerca documenti",
+ "@searchDocuments": {},
+ "resetFilter": "Pulisci filtri",
+ "@resetFilter": {},
+ "lastMonth": "Ultimo mese",
+ "@lastMonth": {},
+ "last7Days": "Ultimi 7 giorni",
+ "@last7Days": {},
+ "last3Months": "Ultimi 3 mesi",
+ "@last3Months": {},
+ "lastYear": "Ultimo anno",
+ "@lastYear": {},
+ "search": "Cerca",
+ "@search": {},
+ "documentsSuccessfullyDeleted": "Documenti eliminati con successo.",
+ "@documentsSuccessfullyDeleted": {},
+ "thereSeemsToBeNothingHere": "Sembra che non ci sia niente qui...",
+ "@thereSeemsToBeNothingHere": {},
+ "oops": "Ops.",
+ "@oops": {},
+ "newDocumentAvailable": "Nuovo documento disponibile!",
+ "@newDocumentAvailable": {},
+ "orderBy": "Ordina per",
+ "@orderBy": {},
+ "thisActionIsIrreversibleDoYouWishToProceedAnyway": "Questa azione è irreversibile. Vuoi procedere comunque?",
+ "@thisActionIsIrreversibleDoYouWishToProceedAnyway": {},
+ "confirmDeletion": "Conferma l'eliminazione",
+ "@confirmDeletion": {},
+ "areYouSureYouWantToDeleteTheFollowingDocuments": "{count, plural, one{Sei sicuro di voler eliminare il seguente documento?} other{Sei sicuro di voler eliminare i seguenti documenti?}}",
+ "@areYouSureYouWantToDeleteTheFollowingDocuments": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "countSelected": "{count} selezionati",
+ "@countSelected": {
+ "description": "Displayed in the appbar when at least one document is selected.",
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "storagePath": "Percorso Archiviazione",
+ "@storagePath": {},
+ "prepareDocument": "Prepara documento",
+ "@prepareDocument": {},
+ "tags": "Etichette",
+ "@tags": {},
+ "documentSuccessfullyUpdated": "Documento aggiornato con successo.",
+ "@documentSuccessfullyUpdated": {},
+ "fileName": "Nome file",
+ "@fileName": {},
+ "synchronizeTitleAndFilename": "Sincronizza titolo e nome file",
+ "@synchronizeTitleAndFilename": {},
+ "reload": "Ricarica",
+ "@reload": {},
+ "documentSuccessfullyUploadedProcessing": "Documento caricato con successo, elaborazione...",
+ "@documentSuccessfullyUploadedProcessing": {},
+ "deleteLabelWarningText": "Questa etichetta contiene riferimenti ad altri documenti. Eliminando questa etichetta, tutti i riferimenti verranno rimossi. Continuare?",
+ "@deleteLabelWarningText": {},
+ "couldNotAcknowledgeTasks": "Impossibile riconoscere le attività.",
+ "@couldNotAcknowledgeTasks": {},
+ "authenticationFailedPleaseTryAgain": "Errore di autenticazione. Riprova.",
+ "@authenticationFailedPleaseTryAgain": {},
+ "anErrorOccurredWhileTryingToAutocompleteYourQuery": "Si è verificato un errore durante il tentativo di completare automaticamente la query.",
+ "@anErrorOccurredWhileTryingToAutocompleteYourQuery": {},
+ "biometricAuthenticationFailed": "Autenticazione biometrica non riuscita.",
+ "@biometricAuthenticationFailed": {},
+ "biometricAuthenticationNotSupported": "Autenticazione biometrica non supportata su questo dispositivo.",
+ "@biometricAuthenticationNotSupported": {},
+ "couldNotBulkEditDocuments": "Impossibile modificare i documenti collettivamente.",
+ "@couldNotBulkEditDocuments": {},
+ "couldNotCreateCorrespondent": "Impossibile creare il corrispondente, per favore riprova.",
+ "@couldNotCreateCorrespondent": {},
+ "couldNotLoadCorrespondents": "Impossibile caricare i corrispondenti.",
+ "@couldNotLoadCorrespondents": {},
+ "couldNotCreateSavedView": "Impossibile creare la vista salvata, riprova.",
+ "@couldNotCreateSavedView": {},
+ "couldNotDeleteSavedView": "Impossibile eliminare la vista salvata, riprova",
+ "@couldNotDeleteSavedView": {},
+ "youAreCurrentlyOffline": "Attualmente sei offline. Assicurati di essere connesso a internet.",
+ "@youAreCurrentlyOffline": {},
+ "couldNotAssignArchiveSerialNumber": "Impossibile assegnare il numero seriale di archivio.",
+ "@couldNotAssignArchiveSerialNumber": {},
+ "couldNotDeleteDocument": "Impossibile eliminare il documento, per favore riprova.",
+ "@couldNotDeleteDocument": {},
+ "couldNotLoadDocuments": "Impossibile caricare il documento, per favore riprova.",
+ "@couldNotLoadDocuments": {},
+ "couldNotLoadDocumentPreview": "Impossibile caricare l'anteprima del documento.",
+ "@couldNotLoadDocumentPreview": {},
+ "couldNotCreateDocument": "Impossibile creare il documento, riprova.",
+ "@couldNotCreateDocument": {},
+ "couldNotLoadDocumentTypes": "Impossibile caricare i tipi di documento, per favore riprova.",
+ "@couldNotLoadDocumentTypes": {},
+ "couldNotUpdateDocument": "Impossibile aggiornare il documento, riprova.",
+ "@couldNotUpdateDocument": {},
+ "couldNotUploadDocument": "Impossibile caricare il documento, per favore riprova.",
+ "@couldNotUploadDocument": {},
+ "invalidCertificateOrMissingPassphrase": "Certificato non valido o passphrase mancante, riprova",
+ "@invalidCertificateOrMissingPassphrase": {},
+ "couldNotLoadSavedViews": "Impossibile caricare le viste salvate.",
+ "@couldNotLoadSavedViews": {},
+ "aClientCertificateWasExpectedButNotSent": "È previsto un certificato del client ma non è stato inviato. Si prega di fornire un certificato del client valido.",
+ "@aClientCertificateWasExpectedButNotSent": {},
+ "userIsNotAuthenticated": "L'utente non è autenticato.",
+ "@userIsNotAuthenticated": {},
+ "requestTimedOut": "La richiesta al server è scaduta.",
+ "@requestTimedOut": {},
+ "anErrorOccurredRemovingTheScans": "Si è verificato un errore durante la rimozione delle scansioni.",
+ "@anErrorOccurredRemovingTheScans": {},
+ "couldNotReachYourPaperlessServer": "Impossibile raggiungere il server Paperless, è in esecuzione?",
+ "@couldNotReachYourPaperlessServer": {},
+ "couldNotLoadSimilarDocuments": "Impossibile caricare documenti simili.",
+ "@couldNotLoadSimilarDocuments": {},
+ "couldNotCreateStoragePath": "Impossibile creare il percorso di archiviazione, per favore riprova.",
+ "@couldNotCreateStoragePath": {},
+ "couldNotLoadStoragePaths": "Impossibile caricare i percorsi di archiviazione.",
+ "@couldNotLoadStoragePaths": {},
+ "couldNotLoadSuggestions": "Impossibile caricare i suggerimenti.",
+ "@couldNotLoadSuggestions": {},
+ "couldNotCreateTag": "Impossibile creare il tag, riprova.",
+ "@couldNotCreateTag": {},
+ "couldNotLoadTags": "Impossibile caricare i tag.",
+ "@couldNotLoadTags": {},
+ "anUnknownErrorOccurred": "Si è verificato un errore sconosciuto.",
+ "@anUnknownErrorOccurred": {},
+ "fileFormatNotSupported": "Questo tipo di file non è supportato.",
+ "@fileFormatNotSupported": {},
+ "report": "REPORT",
+ "@report": {},
+ "absolute": "Assoluto",
+ "@absolute": {},
+ "hintYouCanAlsoSpecifyRelativeValues": "Suggerimento: Oltre alle date specifiche, è possibile specificare un intervallo tra date.",
+ "@hintYouCanAlsoSpecifyRelativeValues": {
+ "description": "Displayed in the extended date range picker"
+ },
+ "amount": "Quantità",
+ "@amount": {},
+ "relative": "Relativo",
+ "@relative": {},
+ "last": "Ultimo",
+ "@last": {},
+ "timeUnit": "Unità di tempo",
+ "@timeUnit": {},
+ "selectDateRange": "Seleziona intervallo date",
+ "@selectDateRange": {},
+ "after": "Dopo",
+ "@after": {},
+ "before": "Prima",
+ "@before": {},
+ "days": "{count, plural, zero{giorni} one{giorno} other{giorni}}",
+ "@days": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNDays": "{count, plural, zero{} one{Ieri} other{Ultimi {count} giorni}}",
+ "@lastNDays": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNMonths": "{count, plural, zero{} one{Ultimo mese} other{Ultimi {count} mesi}}",
+ "@lastNMonths": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNWeeks": "{count, plural, zero{} one{Ultima settimana} other{Ultime {count} settimane}}",
+ "@lastNWeeks": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNYears": "{count, plural, zero{} one{Ultimo anno} other{Ultimi {count} anni}}",
+ "@lastNYears": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "months": "{count, plural, zero{} one{mese} other{mesi}}",
+ "@months": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "weeks": "{count, plural, zero{} one{settimana} other{settimane}}",
+ "@weeks": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "years": "{count, plural, zero{} one{anno} other{anni}}",
+ "@years": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "gotIt": "Capito!",
+ "@gotIt": {},
+ "cancel": "Annulla",
+ "@cancel": {},
+ "close": "Chiudi",
+ "@close": {},
+ "create": "Crea documento",
+ "@create": {},
+ "delete": "Cancella",
+ "@delete": {},
+ "edit": "Modifica",
+ "@edit": {},
+ "ok": "Conferma",
+ "@ok": {},
+ "save": "Salva",
+ "@save": {},
+ "select": "Seleziona",
+ "@select": {},
+ "saveChanges": "Salva modifiche",
+ "@saveChanges": {},
+ "upload": "Upload",
+ "@upload": {},
+ "youreOffline": "Sei offline.",
+ "@youreOffline": {},
+ "deleteDocument": "Elimina documento",
+ "@deleteDocument": {
+ "description": "Used as an action label on each inbox item"
+ },
+ "removeDocumentFromInbox": "Documento rimosso dalla casella in arrivo.",
+ "@removeDocumentFromInbox": {},
+ "areYouSureYouWantToMarkAllDocumentsAsSeen": "Sei sicuro di voler contrassegnare tutti i documenti come visti? Questo eseguirà un'operazione di modifica di massa rimuovendo tutti i tag di posta in arrivo dai documenti. Questa azione non è reversibile! Sei sicuro di voler continuare?",
+ "@areYouSureYouWantToMarkAllDocumentsAsSeen": {},
+ "markAllAsSeen": "Contrassegna come Aperto?",
+ "@markAllAsSeen": {},
+ "allSeen": "Tutto visto",
+ "@allSeen": {},
+ "markAsSeen": "Contrassegna come Aperto",
+ "@markAsSeen": {},
+ "refresh": "Aggiorna",
+ "@refresh": {},
+ "youDoNotHaveUnseenDocuments": "Non hai documenti non visti.",
+ "@youDoNotHaveUnseenDocuments": {},
+ "quickAction": "Azione rapida",
+ "@quickAction": {},
+ "suggestionSuccessfullyApplied": "Suggerimento applicato con successo.",
+ "@suggestionSuccessfullyApplied": {},
+ "today": "Oggi",
+ "@today": {},
+ "undo": "Annullare",
+ "@undo": {},
+ "nUnseen": "{count} non visti",
+ "@nUnseen": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "swipeLeftToMarkADocumentAsSeen": "Suggerimento: Scorri a sinistra per contrassegnare un documento come visto e rimuovere tutti i tag di posta in arrivo dal documento.",
+ "@swipeLeftToMarkADocumentAsSeen": {},
+ "yesterday": "Ieri",
+ "@yesterday": {},
+ "anyAssigned": "Qualunque assegnato",
+ "@anyAssigned": {},
+ "noItemsFound": "Nessun elemento trovato!",
+ "@noItemsFound": {},
+ "caseIrrelevant": "Maiuscolo/Minuscolo Irrilevante",
+ "@caseIrrelevant": {},
+ "matchingAlgorithm": "Algoritmo Di Corrispondenza",
+ "@matchingAlgorithm": {},
+ "match": "Corrispondenza",
+ "@match": {},
+ "name": "Nome",
+ "@name": {},
+ "notAssigned": "Non assegnato",
+ "@notAssigned": {},
+ "addNewCorrespondent": "Aggiungi nuovo corrispondente",
+ "@addNewCorrespondent": {},
+ "noCorrespondentsSetUp": "Sembra che non abbiate alcun corrispondente impostato.",
+ "@noCorrespondentsSetUp": {},
+ "correspondents": "Corrispondenti",
+ "@correspondents": {},
+ "addNewDocumentType": "Aggiungi nuovo tipo di documento",
+ "@addNewDocumentType": {},
+ "noDocumentTypesSetUp": "Sembra che non abbiate impostato nessun tipo di documento.",
+ "@noDocumentTypesSetUp": {},
+ "documentTypes": "Tipi Di Documento",
+ "@documentTypes": {},
+ "addNewStoragePath": "Aggiungi un nuovo percorso di archiviazione",
+ "@addNewStoragePath": {},
+ "noStoragePathsSetUp": "Sembra che non abbiate impostato alcun percorso di archiviazione.",
+ "@noStoragePathsSetUp": {},
+ "storagePaths": "Percorsi Di Archiviazione",
+ "@storagePaths": {},
+ "addNewTag": "Aggiungi nuova etichetta",
+ "@addNewTag": {},
+ "noTagsSetUp": "Sembra che non abbiate impostato alcuna etichetta.",
+ "@noTagsSetUp": {},
+ "linkedDocuments": "Documenti Collegati",
+ "@linkedDocuments": {},
+ "advancedSettings": "Impostazioni avanzate",
+ "@advancedSettings": {},
+ "passphrase": "Passphrase",
+ "@passphrase": {},
+ "configureMutualTLSAuthentication": "Configura Autenticazione TLS Mutua",
+ "@configureMutualTLSAuthentication": {},
+ "invalidCertificateFormat": "Formato del certificato non valido, è consentito solo .pfx",
+ "@invalidCertificateFormat": {},
+ "clientcertificate": "Certificato Client",
+ "@clientcertificate": {},
+ "selectFile": "Seleziona file...",
+ "@selectFile": {},
+ "continueLabel": "Continua",
+ "@continueLabel": {},
+ "incorrectOrMissingCertificatePassphrase": "Passphrase del certificato errata o mancante.",
+ "@incorrectOrMissingCertificatePassphrase": {},
+ "connect": "Connessione",
+ "@connect": {},
+ "password": "Password",
+ "@password": {},
+ "passwordMustNotBeEmpty": "La password non deve essere vuota.",
+ "@passwordMustNotBeEmpty": {},
+ "connectionTimedOut": "Connessione scaduta.",
+ "@connectionTimedOut": {},
+ "loginPageReachabilityMissingClientCertificateText": "È previsto un certificato client ma non è stato inviato. Si prega di fornire un certificato client valido.",
+ "@loginPageReachabilityMissingClientCertificateText": {},
+ "couldNotEstablishConnectionToTheServer": "Impossibile stabilire una connessione con il server.",
+ "@couldNotEstablishConnectionToTheServer": {},
+ "connectionSuccessfulylEstablished": "Connessione stabilita con successo.",
+ "@connectionSuccessfulylEstablished": {},
+ "hostCouldNotBeResolved": "L'host non può essere risolto. Controlla l'indirizzo del server e la tua connessione internet. ",
+ "@hostCouldNotBeResolved": {},
+ "serverAddress": "Indirizzo Server",
+ "@serverAddress": {},
+ "invalidAddress": "Indirizzo non valido.",
+ "@invalidAddress": {},
+ "serverAddressMustIncludeAScheme": "L'indirizzo del server deve includere uno schema.",
+ "@serverAddressMustIncludeAScheme": {},
+ "serverAddressMustNotBeEmpty": "L'indirizzo del server non deve essere vuoto.",
+ "@serverAddressMustNotBeEmpty": {},
+ "signIn": "Accedi",
+ "@signIn": {},
+ "loginPageSignInTitle": "Accedi",
+ "@loginPageSignInTitle": {},
+ "signInToServer": "Accedi a {serverAddress}",
+ "@signInToServer": {
+ "placeholders": {
+ "serverAddress": {}
+ }
+ },
+ "connectToPaperless": "Connettiti a Paperless",
+ "@connectToPaperless": {},
+ "username": "Nome Utente",
+ "@username": {},
+ "usernameMustNotBeEmpty": "Nome Utente non può essere vuoto.",
+ "@usernameMustNotBeEmpty": {},
+ "documentContainsAllOfTheseWords": "Il documento contiene tutte queste parole",
+ "@documentContainsAllOfTheseWords": {},
+ "all": "Tutti",
+ "@all": {},
+ "documentContainsAnyOfTheseWords": "Il documento contiene una di queste parole",
+ "@documentContainsAnyOfTheseWords": {},
+ "any": "Qualsiasi",
+ "@any": {},
+ "learnMatchingAutomatically": "Impara automaticamente la corrispondenza",
+ "@learnMatchingAutomatically": {},
+ "auto": "Auto",
+ "@auto": {},
+ "documentContainsThisString": "Il documento contiene questa stringa",
+ "@documentContainsThisString": {},
+ "exact": "Esatto",
+ "@exact": {},
+ "documentContainsAWordSimilarToThisWord": "Il documento contiene una parola simile a questa",
+ "@documentContainsAWordSimilarToThisWord": {},
+ "fuzzy": "Simile",
+ "@fuzzy": {},
+ "documentMatchesThisRegularExpression": "Il documento corrisponde a questa espressione",
+ "@documentMatchesThisRegularExpression": {},
+ "regularExpression": "Espressione",
+ "@regularExpression": {},
+ "anInternetConnectionCouldNotBeEstablished": "Impossibile stabilire una connessione Internet.",
+ "@anInternetConnectionCouldNotBeEstablished": {},
+ "done": "Fatto",
+ "@done": {},
+ "next": "Successivo",
+ "@next": {},
+ "couldNotAccessReceivedFile": "Impossibile accedere al file ricevuto. Prova ad aprire l'app prima di condividere.",
+ "@couldNotAccessReceivedFile": {},
+ "newView": "Nuova visualizzazione",
+ "@newView": {},
+ "createsASavedViewBasedOnTheCurrentFilterCriteria": "Crea una nuova visualizzazione in base ai criteri di filtro correnti.",
+ "@createsASavedViewBasedOnTheCurrentFilterCriteria": {},
+ "createViewsToQuicklyFilterYourDocuments": "Crea viste per filtrare rapidamente i tuoi documenti.",
+ "@createViewsToQuicklyFilterYourDocuments": {},
+ "nFiltersSet": "{count, plural, zero{{count} filtri impostati} one{{count} filtro impostato} other{{count} filtri impostati}}",
+ "@nFiltersSet": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "showInSidebar": "Visualizza nella barra laterale",
+ "@showInSidebar": {},
+ "showOnDashboard": "Mostra nella dashboard",
+ "@showOnDashboard": {},
+ "views": "Visualizzazioni",
+ "@views": {},
+ "clearAll": "Cancella tutto",
+ "@clearAll": {},
+ "scan": "Scansione",
+ "@scan": {},
+ "previewScan": "Anteprima",
+ "@previewScan": {},
+ "scrollToTop": "Scorri all'inizio",
+ "@scrollToTop": {},
+ "paperlessServerVersion": "Versione server Paperless",
+ "@paperlessServerVersion": {},
+ "darkTheme": "Tema Scuro",
+ "@darkTheme": {},
+ "lightTheme": "Tema Chiaro",
+ "@lightTheme": {},
+ "systemTheme": "Usa tema di sistema",
+ "@systemTheme": {},
+ "appearance": "Aspetto",
+ "@appearance": {},
+ "languageAndVisualAppearance": "Lingua e aspetto grafico",
+ "@languageAndVisualAppearance": {},
+ "applicationSettings": "Applicazione",
+ "@applicationSettings": {},
+ "colorSchemeHint": "Scegli tra uno schema di colori classico ispirato al verde tradizionale di Paperless o usa lo schema di colori dinamico basato sul tema del sistema.",
+ "@colorSchemeHint": {},
+ "colorSchemeNotSupportedWarning": "Il tema dinamico è supportato solo per i dispositivi che eseguono Android 12 e versioni precedenti. Selezionando l'opzione 'Dinamica' potrebbe non avere alcun effetto in base all'implementazione del tuo sistema operativo.",
+ "@colorSchemeNotSupportedWarning": {},
+ "colors": "Colori",
+ "@colors": {},
+ "language": "Lingua",
+ "@language": {},
+ "security": "Sicurezza",
+ "@security": {},
+ "mangeFilesAndStorageSpace": "Gestisci file e spazio di archiviazione",
+ "@mangeFilesAndStorageSpace": {},
+ "storage": "Memoria",
+ "@storage": {},
+ "dark": "Scuro",
+ "@dark": {},
+ "light": "Chiaro",
+ "@light": {},
+ "system": "Sistema",
+ "@system": {},
+ "ascending": "Crescente",
+ "@ascending": {},
+ "descending": "Decrescente",
+ "@descending": {},
+ "storagePathDay": "giorno",
+ "@storagePathDay": {},
+ "storagePathMonth": "mese",
+ "@storagePathMonth": {},
+ "storagePathYear": "anno",
+ "@storagePathYear": {},
+ "color": "Colore",
+ "@color": {},
+ "filterTags": "Filtra Tag...",
+ "@filterTags": {},
+ "inboxTag": "Inbox-Tag",
+ "@inboxTag": {},
+ "uploadInferValuesHint": "Se specifichi dei valori per questi campi, paperless non li compilerà in automatico. Se vuoi che questi campi siano compilati automaticamente dal server, lasciali in bianco.",
+ "@uploadInferValuesHint": {},
+ "useTheConfiguredBiometricFactorToAuthenticate": "Usa il riconoscimento biometrico configurato per autenticarti e sbloccare i tuoi documenti.",
+ "@useTheConfiguredBiometricFactorToAuthenticate": {},
+ "verifyYourIdentity": "Verifica la tua identità",
+ "@verifyYourIdentity": {},
+ "verifyIdentity": "Verifica Identità",
+ "@verifyIdentity": {},
+ "detailed": "Dettagliato",
+ "@detailed": {},
+ "grid": "Griglia",
+ "@grid": {},
+ "list": "Lista",
+ "@list": {},
+ "remove": "Rimuovi",
+ "removeQueryFromSearchHistory": "Rimuovere dalla cronologia di ricerca?",
+ "dynamicColorScheme": "Dinamico",
+ "@dynamicColorScheme": {},
+ "classicColorScheme": "Classico",
+ "@classicColorScheme": {},
+ "notificationDownloadComplete": "Download completato",
+ "@notificationDownloadComplete": {
+ "description": "Notification title when a download has been completed."
+ },
+ "notificationDownloadingDocument": "Download del documento",
+ "@notificationDownloadingDocument": {
+ "description": "Notification title shown when a document download is pending"
+ },
+ "archiveSerialNumberUpdated": "Numero seriale di archivio aggiornato.",
+ "@archiveSerialNumberUpdated": {
+ "description": "Message shown when the ASN has been updated."
+ },
+ "donateCoffee": "Offrimi un caffè",
+ "@donateCoffee": {
+ "description": "Label displayed in the app drawer"
+ },
+ "thisFieldIsRequired": "Questo campo è obbligatorio!",
+ "@thisFieldIsRequired": {
+ "description": "Message shown below the form field when a required field has not been filled out."
+ },
+ "confirm": "Conferma",
+ "confirmAction": "Conferma azione",
+ "@confirmAction": {
+ "description": "Typically used as a title to confirm a previously selected action"
+ },
+ "areYouSureYouWantToContinue": "Sei sicuro di voler continuare?",
+ "bulkEditTagsAddMessage": "{count, plural, one{Questa operazione aggiungerà i tag {tags} al documento selezionato.} other{Questa operazione aggiungerà i tag {tags} ai {count} documenti selezionati.}}",
+ "@bulkEditTagsAddMessage": {
+ "description": "Message of the confirmation dialog when bulk adding tags."
+ },
+ "bulkEditTagsRemoveMessage": "{count, plural, one{Questa operazione rimuoverà i tag {tags} dal documento selezionato.} other{Questa operazione rimuoverà i tag {tags} dai {count} documenti selezionati.}}",
+ "@bulkEditTagsRemoveMessage": {
+ "description": "Message of the confirmation dialog when bulk removing tags."
+ },
+ "bulkEditTagsModifyMessage": "{count, plural, one{Questa operazione aggiungerà i tag {addTags} e rimuoverà i tag {removeTags} dal documento selezionato.} other{Questa operazione aggiungerà i tag {addTags} e rimuoverà i tag {removeTags} dai {count} documenti selezionati.}}",
+ "@bulkEditTagsModifyMessage": {
+ "description": "Message of the confirmation dialog when both adding and removing tags."
+ },
+ "bulkEditCorrespondentAssignMessage": "{count, plural, one{Questa operazione assegnerà i corrispondenti {correspondent} al documento selezionato.} other{Questa operazione assegnerà i corrispondenti {correspondent} ai {count} documenti selezionati.}}",
+ "bulkEditDocumentTypeAssignMessage": "{count, plural, one{Questa operazione aggiungerà il tipo documento {docType} al documento selezionato.} other{Questa operazione aggiungerà il tipo documento {docType} ai {count} documenti selezionati.}}",
+ "bulkEditStoragePathAssignMessage": "{count, plural, one{Questa operazione assegnerà il percorso di memorizzazione {path} al documento selezionato.} other{Questa operazione assegnerà il percorso di memorizzazione {path} ai {count} documenti selezionati.}}",
+ "bulkEditCorrespondentRemoveMessage": "{count, plural, one{Questa operazione rimuoverà il correspondente dal documento selezionato.} other{Questa operazione rimuoverà il corrispondente dai {count} documenti selezionati.}}",
+ "bulkEditDocumentTypeRemoveMessage": "{count, plural, one{Questa operazione rimuoverà il tipo dal documento selezionato.} other{Questa operazione rimuoverà il tipo dai {count} documenti selezionati.}}",
+ "bulkEditStoragePathRemoveMessage": "{count, plural, one{Questa operazione rimuoverà il percorso di memorizzazione dal documento selezionato.} other{Questa operazione rimuoverà il percorso di memorizzazione dai {count} documenti selezionati.}}",
+ "anyTag": "Qualsiasi",
+ "@anyTag": {
+ "description": "Label shown when any tag should be filtered"
+ },
+ "allTags": "Tutti",
+ "@allTags": {
+ "description": "Label shown when a document has to be assigned to all selected tags"
+ },
+ "switchingAccountsPleaseWait": "Cambio account. Attendere prego...",
+ "@switchingAccountsPleaseWait": {
+ "description": "Message shown while switching accounts is in progress."
+ },
+ "testConnection": "Test di connessione",
+ "@testConnection": {
+ "description": "Button label shown on login page. Allows user to test whether the server is reachable or not."
+ },
+ "accounts": "Account",
+ "@accounts": {
+ "description": "Title of the account management dialog"
+ },
+ "addAccount": "Aggiungi account",
+ "@addAccount": {
+ "description": "Label of add account action"
+ },
+ "switchAccount": "Cambia",
+ "@switchAccount": {
+ "description": "Label for switch account action"
+ },
+ "logout": "Esci",
+ "@logout": {
+ "description": "Generic Logout label"
+ },
+ "switchAccountTitle": "Cambia account",
+ "@switchAccountTitle": {
+ "description": "Title of the dialog shown after adding an account, asking the user whether to switch to the newly added account or not."
+ },
+ "switchToNewAccount": "Vuoi passare al nuovo account? Puoi tornare indietro in qualsiasi momento.",
+ "@switchToNewAccount": {
+ "description": "Content of the dialog shown after adding an account, asking the user whether to switch to the newly added account or not."
+ },
+ "sourceCode": "Codice Sorgente",
+ "findTheSourceCodeOn": "Trova il codice sorgente attivo",
+ "@findTheSourceCodeOn": {
+ "description": "Text before link to Paperless Mobile GitHub"
+ },
+ "rememberDecision": "Ricorda questa scelta",
+ "defaultDownloadFileType": "Download Tipo Di File Predefinito",
+ "@defaultDownloadFileType": {
+ "description": "Label indicating the default filetype to download (one of archived, original and always ask)"
+ },
+ "defaultShareFileType": "Tipo Di File Condivisione Predefinito",
+ "@defaultShareFileType": {
+ "description": "Label indicating the default filetype to share (one of archived, original and always ask)"
+ },
+ "alwaysAsk": "Chiedi sempre",
+ "@alwaysAsk": {
+ "description": "Option to choose when the app should always ask the user which filetype to use"
+ },
+ "disableMatching": "Non taggare i documenti automaticamente",
+ "@disableMatching": {
+ "description": "One of the options for automatic tagging of documents"
+ },
+ "none": "Nessuna",
+ "@none": {
+ "description": "One of available enum values of matching algorithm for tags"
+ },
+ "logInToExistingAccount": "Accedi ad un account esistente",
+ "@logInToExistingAccount": {
+ "description": "Title shown on login page if at least one user is already known to the app."
+ },
+ "print": "Stampa",
+ "@print": {
+ "description": "Tooltip for print button"
+ },
+ "managePermissions": "Gestione autorizzazioni",
+ "@managePermissions": {
+ "description": "Button which leads user to manage permissions page"
+ },
+ "errorRetrievingServerVersion": "Si è verificato un errore nella risoluzione della versione del server.",
+ "@errorRetrievingServerVersion": {
+ "description": "Message shown at the bottom of the settings page when the remote server version could not be resolved."
+ },
+ "resolvingServerVersion": "Recupero versione server...",
+ "@resolvingServerVersion": {
+ "description": "Message shown while the app is loading the remote server version."
+ },
+ "goToLogin": "Vai al login",
+ "@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": "Esporta",
+ "@export": {
+ "description": "Label for button that exports scanned images to pdf (before upload)"
+ },
+ "invalidFilenameCharacter": "Carattere(i) non valido(i) trovato(i) nel nome del file: {characters}",
+ "@invalidFilenameCharacter": {
+ "description": "For validating filename in export dialogue"
+ },
+ "exportScansToPdf": "Esporta scansioni in PDF",
+ "@exportScansToPdf": {
+ "description": "title of the alert dialog when exporting scans to pdf"
+ },
+ "allScansWillBeMerged": "Tutte le scansioni saranno unite in un singolo file PDF.",
+ "behavior": "Comportamento",
+ "@behavior": {
+ "description": "Title of the settings concerning app beahvior"
+ },
+ "theme": "Tema",
+ "@theme": {
+ "description": "Title of the theme mode setting"
+ },
+ "clearCache": "Svuota cache",
+ "@clearCache": {
+ "description": "Title of the clear cache setting"
+ },
+ "freeBytes": "Libero {byteString}",
+ "@freeBytes": {
+ "description": "Text shown for clear storage settings"
+ },
+ "calculatingDots": "Calcolo...",
+ "@calculatingDots": {
+ "description": "Text shown when the byte size is still being calculated"
+ },
+ "freedDiskSpace": "Liberato con successo {bytes} spazio su disco.",
+ "@freedDiskSpace": {
+ "description": "Message shown after clearing storage"
+ },
+ "uploadScansAsPdf": "Carica scansioni in PDF",
+ "@uploadScansAsPdf": {
+ "description": "Title of the setting which toggles whether scans are always uploaded as pdf"
+ },
+ "convertSinglePageScanToPdf": "Convertire sempre le scansioni di singola pagina in PDF prima di caricare",
+ "@convertSinglePageScanToPdf": {
+ "description": "description of the upload scans as pdf setting"
+ },
+ "loginRequiredPermissionsHint": "L'utilizzo di Paperless Mobile richiede una serie minima di autorizzazioni utente a partire da paperless-ngx 1.14.0 e superiore. Pertanto, assicurati che l'utente abbia il permesso di visualizzare altri utenti (Utente → Visualizza) e le impostazioni (UISettings → Visualizza). Se non hai questi permessi, contatta un amministratore del tuo server paperless-ngx.",
+ "@loginRequiredPermissionsHint": {
+ "description": "Hint shown on the login page informing the user of the required permissions to use the app."
+ },
+ "missingPermissions": "Non disponi dei permessi necessari per eseguire quest'azione.",
+ "@missingPermissions": {
+ "description": "Message shown in a snackbar when a user without the reequired permissions performs an action."
+ },
+ "editView": "Modifica Visualizzazione",
+ "@editView": {
+ "description": "Title of the edit saved view page"
+ },
+ "donate": "Fai una donazione",
+ "@donate": {
+ "description": "Label of the in-app donate button"
+ },
+ "donationDialogContent": "Grazie per aver preso in considerazione di supportare questa app! In linea con le politiche di pagamento di Google e Apple, non è possibile visualizzare alcun link che porti a donazioni in-app. Non è consentito nemmeno il collegamento alla pagina della repository del progetto. Pertanto, sarà presente nella sezione 'Donazioni' nel README del progetto. Il tuo supporto è molto apprezzato e mantiene vivo lo sviluppo di questa app. Grazie!",
+ "@donationDialogContent": {
+ "description": "Text displayed in the donation dialog"
+ },
+ "noDocumentsFound": "Nessun documento trovato.",
+ "@noDocumentsFound": {
+ "description": "Message shown when no documents were found."
+ },
+ "couldNotDeleteCorrespondent": "Impossibile eliminare il corrispondente, per favore riprova.",
+ "@couldNotDeleteCorrespondent": {
+ "description": "Message shown in snackbar when a correspondent could not be deleted."
+ },
+ "couldNotDeleteDocumentType": "Impossibile eliminare il tipo di documento, per favore riprova.",
+ "@couldNotDeleteDocumentType": {
+ "description": "Message shown when a document type could not be deleted"
+ },
+ "couldNotDeleteTag": "Impossibile creare il tag, riprova.",
+ "@couldNotDeleteTag": {
+ "description": "Message shown when a tag could not be deleted"
+ },
+ "couldNotDeleteStoragePath": "Impossibile eliminare il percorso di archiviazione, per favore riprova.",
+ "@couldNotDeleteStoragePath": {
+ "description": "Message shown when a storage path could not be deleted"
+ },
+ "couldNotUpdateCorrespondent": "Impossibile aggiornare il corrispondente, per favore riprova.",
+ "@couldNotUpdateCorrespondent": {
+ "description": "Message shown when a correspondent could not be updated"
+ },
+ "couldNotUpdateDocumentType": "Impossibile aggiornare il tipo di documento, per favore riprova.",
+ "@couldNotUpdateDocumentType": {
+ "description": "Message shown when a document type could not be updated"
+ },
+ "couldNotUpdateTag": "Impossibile aggiornare il tag, per favore riprova.",
+ "@couldNotUpdateTag": {
+ "description": "Message shown when a tag could not be updated"
+ },
+ "couldNotLoadServerInformation": "Impossibile caricare le informazioni del server.",
+ "@couldNotLoadServerInformation": {
+ "description": "Message shown when the server information could not be loaded"
+ },
+ "couldNotLoadStatistics": "Impossibile caricare le statistiche del server.",
+ "@couldNotLoadStatistics": {
+ "description": "Message shown when the server statistics could not be loaded"
+ },
+ "couldNotLoadUISettings": "Impossibile caricare impostazioni UI Interfaccia Utente.",
+ "@couldNotLoadUISettings": {
+ "description": "Message shown when the UI settings could not be loaded"
+ },
+ "couldNotLoadTasks": "Impossibile caricare le attività.",
+ "@couldNotLoadTasks": {
+ "description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
+ },
+ "userNotFound": "Impossibile trovare l'utente.",
+ "@userNotFound": {
+ "description": "Message shown when the specified user (e.g. by id) could not be found"
+ },
+ "couldNotUpdateSavedView": "Impossibile creare la vista salvata, riprova.",
+ "@couldNotUpdateSavedView": {
+ "description": "Message shown when a saved view could not be updated"
+ },
+ "couldNotUpdateStoragePath": "Impossibile aggiornare il percorso di archiviazione, per favore riprova.",
+ "savedViewSuccessfullyUpdated": "Visualizzazione salvata aggiornata correttamente.",
+ "@savedViewSuccessfullyUpdated": {
+ "description": "Message shown when a saved view was successfully updated."
+ },
+ "discardChanges": "Annulla modifiche?",
+ "@discardChanges": {
+ "description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
+ },
+ "savedViewChangedDialogContent": "Le condizioni del filtro della vista attiva sono cambiate. Reimpostando il filtro, queste modifiche andranno perse. Desideri continuare?",
+ "@savedViewChangedDialogContent": {
+ "description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
+ },
+ "createFromCurrentFilter": "Crea dal filtro corrente",
+ "@createFromCurrentFilter": {
+ "description": "Tooltip of the \"New saved view\" button"
+ },
+ "home": "Home",
+ "@home": {
+ "description": "Label of the \"Home\" route"
+ },
+ "welcomeUser": "Benvenuto, {name}!",
+ "@welcomeUser": {
+ "description": "Top message shown on the home page"
+ },
+ "statistics": "Statistiche",
+ "documentsInInbox": "Documenti nella posta in arrivo",
+ "totalDocuments": "Totale documenti",
+ "totalCharacters": "Totale caratteri",
+ "showAll": "Mostra tutto",
+ "@showAll": {
+ "description": "Button label shown on a saved view preview to open this view in the documents page"
+ },
+ "userAlreadyExists": "Utente già esistente.",
+ "@userAlreadyExists": {
+ "description": "Error message shown when the user tries to add an already existing account."
+ },
+ "youDidNotSaveAnyViewsYet": "Non hai ancora salvato nessuna vista, creane una e verrà mostrata qui.",
+ "@youDidNotSaveAnyViewsYet": {
+ "description": "Message shown when there are no saved views yet."
+ },
+ "tryAgain": "Riprova",
+ "discardFile": "Eliminare il file?",
+ "discard": "Elimina",
+ "backToLogin": "Torna al login",
+ "skipEditingReceivedFiles": "Salta la modifica dei file ricevuti",
+ "uploadWithoutPromptingUploadForm": "Carica sempre senza richiedere il modulo di caricamento quando condividi file con l'app.",
+ "authenticatingDots": "Accesso in corso...",
+ "@authenticatingDots": {
+ "description": "Message shown when the app is authenticating the user"
+ },
+ "persistingUserInformation": "Salvataggio informazioni utente...",
+ "fetchingUserInformation": "Recupero informazioni utente...",
+ "@fetchingUserInformation": {
+ "description": "Message shown when the app loads user data from the server"
+ },
+ "restoringSession": "Ripristino sessione...",
+ "@restoringSession": {
+ "description": "Message shown when the user opens the app and the previous user is tried to be authenticated and logged in"
+ },
+ "documentsAssigned": "{count, plural, zero{Nessun documento} one{1 documento} other{{count} documenti}}",
+ "@documentsAssigned": {
+ "description": "Text shown with a correspondent, document type etc. to indicate the number of documents this filter will maximally yield."
+ },
+ "discardChangesWarning": "Sono state apportate modifiche non salvate. Continuando tutte le modifiche andranno perse. Eliminare queste modifiche?",
+ "@discardChangesWarning": {
+ "description": "Warning message shown when the user tries to close a route without saving the changes."
+ },
+ "changelog": "Changelog",
+ "noLogsFoundOn": "Nessun log trovato il {date}.",
+ "logfileBottomReached": "Hai raggiunto la fine di questo file di log.",
+ "appLogs": "App logs {date}",
+ "saveLogsToFile": "Salva i log su file",
+ "copyToClipboard": "Copia negli appunti",
+ "couldNotLoadLogfileFrom": "Impossibile caricare il file di log da {date}.",
+ "loadingLogsFrom": "Caricamento log da {date}...",
+ "clearLogs": "Cancella log da {date}",
+ "showPdf": "Mostra PDF",
+ "@showPdf": {
+ "description": "Tooltip shown on the \"show pdf\" button on the document edit page"
+ },
+ "hidePdf": "Nascondi PDF",
+ "@hidePdf": {
+ "description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
+ },
+ "misc": "Varie",
+ "loggingOut": "Uscita in corso...",
+ "testingConnection": "Verifica connessione...",
+ "@testingConnection": {
+ "description": "Text shown while the app tries to establish a connection to the specified host."
+ },
+ "version": "Versione {versionCode}",
+ "notes": "{count, plural, zero{Nota} one{Nota} other{Note}}",
+ "addNote": "Aggiungi nota",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
+}
\ No newline at end of file
diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb
index 84674b1..4f8f10b 100644
--- a/lib/l10n/intl_nl.arb
+++ b/lib/l10n/intl_nl.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb
index 4f15c72..b55d093 100644
--- a/lib/l10n/intl_pl.arb
+++ b/lib/l10n/intl_pl.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb
new file mode 100644
index 0000000..4f8f10b
--- /dev/null
+++ b/lib/l10n/intl_ro.arb
@@ -0,0 +1,1048 @@
+{
+ "developedBy": "Developed by {name}.",
+ "@developedBy": {
+ "placeholders": {
+ "name": {}
+ }
+ },
+ "addAnotherAccount": "Add another account",
+ "@addAnotherAccount": {},
+ "account": "Account",
+ "@account": {},
+ "addCorrespondent": "New Correspondent",
+ "@addCorrespondent": {
+ "description": "Title when adding a new correspondent"
+ },
+ "addDocumentType": "New Document Type",
+ "@addDocumentType": {
+ "description": "Title when adding a new document type"
+ },
+ "addStoragePath": "New Storage Path",
+ "@addStoragePath": {
+ "description": "Title when adding a new storage path"
+ },
+ "addTag": "New Tag",
+ "@addTag": {
+ "description": "Title when adding a new tag"
+ },
+ "aboutThisApp": "About this app",
+ "@aboutThisApp": {
+ "description": "Label for about this app tile displayed in the drawer"
+ },
+ "loggedInAs": "Logged in as {name}",
+ "@loggedInAs": {
+ "placeholders": {
+ "name": {}
+ }
+ },
+ "disconnect": "Disconnect",
+ "@disconnect": {
+ "description": "Logout button label"
+ },
+ "reportABug": "Report a Bug",
+ "@reportABug": {},
+ "settings": "Settings",
+ "@settings": {},
+ "authenticateOnAppStart": "Authenticate on app start",
+ "@authenticateOnAppStart": {
+ "description": "Description of the biometric authentication settings tile"
+ },
+ "biometricAuthentication": "Biometric authentication",
+ "@biometricAuthentication": {},
+ "authenticateToToggleBiometricAuthentication": "{mode, select, enable{Authenticate to enable biometric authentication} disable{Authenticate to disable biometric authentication} other{}}",
+ "@authenticateToToggleBiometricAuthentication": {
+ "placeholders": {
+ "mode": {}
+ }
+ },
+ "documents": "Documents",
+ "@documents": {},
+ "inbox": "Inbox",
+ "@inbox": {},
+ "labels": "Labels",
+ "@labels": {},
+ "scanner": "Scanner",
+ "@scanner": {},
+ "startTyping": "Start typing...",
+ "@startTyping": {},
+ "doYouReallyWantToDeleteThisView": "Do you really want to delete this view?",
+ "@doYouReallyWantToDeleteThisView": {},
+ "deleteView": "Delete view {name}?",
+ "@deleteView": {},
+ "addedAt": "Added at",
+ "@addedAt": {},
+ "archiveSerialNumber": "Archive Serial Number",
+ "@archiveSerialNumber": {},
+ "asn": "ASN",
+ "@asn": {},
+ "correspondent": "Correspondent",
+ "@correspondent": {},
+ "createdAt": "Created at",
+ "@createdAt": {},
+ "documentSuccessfullyDeleted": "Document successfully deleted.",
+ "@documentSuccessfullyDeleted": {},
+ "assignAsn": "Assign ASN",
+ "@assignAsn": {},
+ "deleteDocumentTooltip": "Delete",
+ "@deleteDocumentTooltip": {
+ "description": "Tooltip shown for the delete button on details page"
+ },
+ "downloadDocumentTooltip": "Download",
+ "@downloadDocumentTooltip": {
+ "description": "Tooltip shown for the download button on details page"
+ },
+ "editDocumentTooltip": "Edit",
+ "@editDocumentTooltip": {
+ "description": "Tooltip shown for the edit button on details page"
+ },
+ "loadFullContent": "Load full content",
+ "@loadFullContent": {},
+ "noAppToDisplayPDFFilesFound": "No app to display PDF files found!",
+ "@noAppToDisplayPDFFilesFound": {},
+ "openInSystemViewer": "Open in system viewer",
+ "@openInSystemViewer": {},
+ "couldNotOpenFilePermissionDenied": "Could not open file: Permission denied.",
+ "@couldNotOpenFilePermissionDenied": {},
+ "previewTooltip": "Preview",
+ "@previewTooltip": {
+ "description": "Tooltip shown for the preview button on details page"
+ },
+ "shareTooltip": "Share",
+ "@shareTooltip": {
+ "description": "Tooltip shown for the share button on details page"
+ },
+ "similarDocuments": "Similar Documents",
+ "@similarDocuments": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "content": "Content",
+ "@content": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "metaData": "Meta Data",
+ "@metaData": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "overview": "Overview",
+ "@overview": {
+ "description": "Label shown in the tabbar on details page"
+ },
+ "documentType": "Document Type",
+ "@documentType": {},
+ "archivedPdf": "Archived (pdf)",
+ "@archivedPdf": {
+ "description": "Option to chose when downloading a document"
+ },
+ "chooseFiletype": "Choose filetype",
+ "@chooseFiletype": {},
+ "original": "Original",
+ "@original": {
+ "description": "Option to chose when downloading a document"
+ },
+ "documentSuccessfullyDownloaded": "Document successfully downloaded.",
+ "@documentSuccessfullyDownloaded": {},
+ "suggestions": "Suggestions: ",
+ "@suggestions": {},
+ "editDocument": "Edit Document",
+ "@editDocument": {},
+ "advanced": "Advanced",
+ "@advanced": {},
+ "apply": "Apply",
+ "@apply": {},
+ "extended": "Extended",
+ "@extended": {},
+ "titleAndContent": "Title & Content",
+ "@titleAndContent": {},
+ "title": "Title",
+ "@title": {},
+ "reset": "Reset",
+ "@reset": {},
+ "filterDocuments": "Filter Documents",
+ "@filterDocuments": {
+ "description": "Title of the document filter"
+ },
+ "originalMD5Checksum": "Original MD5-Checksum",
+ "@originalMD5Checksum": {},
+ "mediaFilename": "Media Filename",
+ "@mediaFilename": {},
+ "originalFileSize": "Original File Size",
+ "@originalFileSize": {},
+ "originalMIMEType": "Original MIME-Type",
+ "@originalMIMEType": {},
+ "modifiedAt": "Modified at",
+ "@modifiedAt": {},
+ "preview": "Preview",
+ "@preview": {
+ "description": "Title of the document preview page"
+ },
+ "scanADocument": "Scan a document",
+ "@scanADocument": {},
+ "noDocumentsScannedYet": "No documents scanned yet.",
+ "@noDocumentsScannedYet": {},
+ "or": "or",
+ "@or": {
+ "description": "Used on the scanner page between both main actions when no scans have been captured."
+ },
+ "deleteAllScans": "Delete all scans",
+ "@deleteAllScans": {},
+ "uploadADocumentFromThisDevice": "Upload a document from this device",
+ "@uploadADocumentFromThisDevice": {
+ "description": "Button label on scanner page"
+ },
+ "noMatchesFound": "No matches found.",
+ "@noMatchesFound": {
+ "description": "Displayed when no documents were found in the document search."
+ },
+ "removeFromSearchHistory": "Remove from search history?",
+ "@removeFromSearchHistory": {},
+ "results": "Results",
+ "@results": {
+ "description": "Label displayed above search results in document search."
+ },
+ "searchDocuments": "Search documents",
+ "@searchDocuments": {},
+ "resetFilter": "Reset filter",
+ "@resetFilter": {},
+ "lastMonth": "Last Month",
+ "@lastMonth": {},
+ "last7Days": "Last 7 Days",
+ "@last7Days": {},
+ "last3Months": "Last 3 Months",
+ "@last3Months": {},
+ "lastYear": "Last Year",
+ "@lastYear": {},
+ "search": "Search",
+ "@search": {},
+ "documentsSuccessfullyDeleted": "Documents successfully deleted.",
+ "@documentsSuccessfullyDeleted": {},
+ "thereSeemsToBeNothingHere": "There seems to be nothing here...",
+ "@thereSeemsToBeNothingHere": {},
+ "oops": "Oops.",
+ "@oops": {},
+ "newDocumentAvailable": "New document available!",
+ "@newDocumentAvailable": {},
+ "orderBy": "Order By",
+ "@orderBy": {},
+ "thisActionIsIrreversibleDoYouWishToProceedAnyway": "This action is irreversible. Do you wish to proceed anyway?",
+ "@thisActionIsIrreversibleDoYouWishToProceedAnyway": {},
+ "confirmDeletion": "Confirm deletion",
+ "@confirmDeletion": {},
+ "areYouSureYouWantToDeleteTheFollowingDocuments": "{count, plural, one{Are you sure you want to delete the following document?} other{Are you sure you want to delete the following documents?}}",
+ "@areYouSureYouWantToDeleteTheFollowingDocuments": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "countSelected": "{count} selected",
+ "@countSelected": {
+ "description": "Displayed in the appbar when at least one document is selected.",
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "storagePath": "Storage Path",
+ "@storagePath": {},
+ "prepareDocument": "Prepare document",
+ "@prepareDocument": {},
+ "tags": "Tags",
+ "@tags": {},
+ "documentSuccessfullyUpdated": "Document successfully updated.",
+ "@documentSuccessfullyUpdated": {},
+ "fileName": "File Name",
+ "@fileName": {},
+ "synchronizeTitleAndFilename": "Synchronize title and filename",
+ "@synchronizeTitleAndFilename": {},
+ "reload": "Reload",
+ "@reload": {},
+ "documentSuccessfullyUploadedProcessing": "Document successfully uploaded, processing...",
+ "@documentSuccessfullyUploadedProcessing": {},
+ "deleteLabelWarningText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
+ "@deleteLabelWarningText": {},
+ "couldNotAcknowledgeTasks": "Could not acknowledge tasks.",
+ "@couldNotAcknowledgeTasks": {},
+ "authenticationFailedPleaseTryAgain": "Authentication failed, please try again.",
+ "@authenticationFailedPleaseTryAgain": {},
+ "anErrorOccurredWhileTryingToAutocompleteYourQuery": "An error ocurred while trying to autocomplete your query.",
+ "@anErrorOccurredWhileTryingToAutocompleteYourQuery": {},
+ "biometricAuthenticationFailed": "Biometric authentication failed.",
+ "@biometricAuthenticationFailed": {},
+ "biometricAuthenticationNotSupported": "Biometric authentication not supported on this device.",
+ "@biometricAuthenticationNotSupported": {},
+ "couldNotBulkEditDocuments": "Could not bulk edit documents.",
+ "@couldNotBulkEditDocuments": {},
+ "couldNotCreateCorrespondent": "Could not create correspondent, please try again.",
+ "@couldNotCreateCorrespondent": {},
+ "couldNotLoadCorrespondents": "Could not load correspondents.",
+ "@couldNotLoadCorrespondents": {},
+ "couldNotCreateSavedView": "Could not create saved view, please try again.",
+ "@couldNotCreateSavedView": {},
+ "couldNotDeleteSavedView": "Could not delete saved view, please try again",
+ "@couldNotDeleteSavedView": {},
+ "youAreCurrentlyOffline": "You are currently offline. Please make sure you are connected to the internet.",
+ "@youAreCurrentlyOffline": {},
+ "couldNotAssignArchiveSerialNumber": "Could not assign archive serial number.",
+ "@couldNotAssignArchiveSerialNumber": {},
+ "couldNotDeleteDocument": "Could not delete document, please try again.",
+ "@couldNotDeleteDocument": {},
+ "couldNotLoadDocuments": "Could not load documents, please try again.",
+ "@couldNotLoadDocuments": {},
+ "couldNotLoadDocumentPreview": "Could not load document preview.",
+ "@couldNotLoadDocumentPreview": {},
+ "couldNotCreateDocument": "Could not create document, please try again.",
+ "@couldNotCreateDocument": {},
+ "couldNotLoadDocumentTypes": "Could not load document types, please try again.",
+ "@couldNotLoadDocumentTypes": {},
+ "couldNotUpdateDocument": "Could not update document, please try again.",
+ "@couldNotUpdateDocument": {},
+ "couldNotUploadDocument": "Could not upload document, please try again.",
+ "@couldNotUploadDocument": {},
+ "invalidCertificateOrMissingPassphrase": "Invalid certificate or missing passphrase, please try again",
+ "@invalidCertificateOrMissingPassphrase": {},
+ "couldNotLoadSavedViews": "Could not load saved views.",
+ "@couldNotLoadSavedViews": {},
+ "aClientCertificateWasExpectedButNotSent": "A client certificate was expected but not sent. Please provide a valid client certificate.",
+ "@aClientCertificateWasExpectedButNotSent": {},
+ "userIsNotAuthenticated": "User is not authenticated.",
+ "@userIsNotAuthenticated": {},
+ "requestTimedOut": "The request to the server timed out.",
+ "@requestTimedOut": {},
+ "anErrorOccurredRemovingTheScans": "An error occurred removing the scans.",
+ "@anErrorOccurredRemovingTheScans": {},
+ "couldNotReachYourPaperlessServer": "Could not reach your Paperless server, is it up and running?",
+ "@couldNotReachYourPaperlessServer": {},
+ "couldNotLoadSimilarDocuments": "Could not load similar documents.",
+ "@couldNotLoadSimilarDocuments": {},
+ "couldNotCreateStoragePath": "Could not create storage path, please try again.",
+ "@couldNotCreateStoragePath": {},
+ "couldNotLoadStoragePaths": "Could not load storage paths.",
+ "@couldNotLoadStoragePaths": {},
+ "couldNotLoadSuggestions": "Could not load suggestions.",
+ "@couldNotLoadSuggestions": {},
+ "couldNotCreateTag": "Could not create tag, please try again.",
+ "@couldNotCreateTag": {},
+ "couldNotLoadTags": "Could not load tags.",
+ "@couldNotLoadTags": {},
+ "anUnknownErrorOccurred": "An unknown error occurred.",
+ "@anUnknownErrorOccurred": {},
+ "fileFormatNotSupported": "This file format is not supported.",
+ "@fileFormatNotSupported": {},
+ "report": "REPORT",
+ "@report": {},
+ "absolute": "Absolute",
+ "@absolute": {},
+ "hintYouCanAlsoSpecifyRelativeValues": "Hint: Apart from concrete dates, you can also specify a time range relative to the current date.",
+ "@hintYouCanAlsoSpecifyRelativeValues": {
+ "description": "Displayed in the extended date range picker"
+ },
+ "amount": "Amount",
+ "@amount": {},
+ "relative": "Relative",
+ "@relative": {},
+ "last": "Last",
+ "@last": {},
+ "timeUnit": "Time unit",
+ "@timeUnit": {},
+ "selectDateRange": "Select date range",
+ "@selectDateRange": {},
+ "after": "After",
+ "@after": {},
+ "before": "Before",
+ "@before": {},
+ "days": "{count, plural, zero{days} one{day} other{days}}",
+ "@days": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNDays": "{count, plural, zero{} one{Yesterday} other{Last {count} days}}",
+ "@lastNDays": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNMonths": "{count, plural, zero{} one{Last month} other{Last {count} months}}",
+ "@lastNMonths": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNWeeks": "{count, plural, zero{} one{Last week} other{Last {count} weeks}}",
+ "@lastNWeeks": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "lastNYears": "{count, plural, zero{} one{Last year} other{Last {count} years}}",
+ "@lastNYears": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "months": "{count, plural, zero{} one{month} other{months}}",
+ "@months": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "weeks": "{count, plural, zero{} one{week} other{weeks}}",
+ "@weeks": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "years": "{count, plural, zero{} one{year} other{years}}",
+ "@years": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "gotIt": "Got it!",
+ "@gotIt": {},
+ "cancel": "Cancel",
+ "@cancel": {},
+ "close": "Close",
+ "@close": {},
+ "create": "Create",
+ "@create": {},
+ "delete": "Delete",
+ "@delete": {},
+ "edit": "Edit",
+ "@edit": {},
+ "ok": "Ok",
+ "@ok": {},
+ "save": "Save",
+ "@save": {},
+ "select": "Select",
+ "@select": {},
+ "saveChanges": "Save changes",
+ "@saveChanges": {},
+ "upload": "Upload",
+ "@upload": {},
+ "youreOffline": "You're offline.",
+ "@youreOffline": {},
+ "deleteDocument": "Delete document",
+ "@deleteDocument": {
+ "description": "Used as an action label on each inbox item"
+ },
+ "removeDocumentFromInbox": "Document removed from inbox.",
+ "@removeDocumentFromInbox": {},
+ "areYouSureYouWantToMarkAllDocumentsAsSeen": "Are you sure you want to mark all documents as seen? This will perform a bulk edit operation removing all inbox tags from the documents. This action is not reversible! Are you sure you want to continue?",
+ "@areYouSureYouWantToMarkAllDocumentsAsSeen": {},
+ "markAllAsSeen": "Mark all as seen?",
+ "@markAllAsSeen": {},
+ "allSeen": "All seen",
+ "@allSeen": {},
+ "markAsSeen": "Mark as seen",
+ "@markAsSeen": {},
+ "refresh": "Refresh",
+ "@refresh": {},
+ "youDoNotHaveUnseenDocuments": "You do not have unseen documents.",
+ "@youDoNotHaveUnseenDocuments": {},
+ "quickAction": "Quick Action",
+ "@quickAction": {},
+ "suggestionSuccessfullyApplied": "Suggestion successfully applied.",
+ "@suggestionSuccessfullyApplied": {},
+ "today": "Today",
+ "@today": {},
+ "undo": "Undo",
+ "@undo": {},
+ "nUnseen": "{count} unseen",
+ "@nUnseen": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "swipeLeftToMarkADocumentAsSeen": "Hint: Swipe left to mark a document as seen and remove all inbox tags from the document.",
+ "@swipeLeftToMarkADocumentAsSeen": {},
+ "yesterday": "Yesterday",
+ "@yesterday": {},
+ "anyAssigned": "Any assigned",
+ "@anyAssigned": {},
+ "noItemsFound": "No items found!",
+ "@noItemsFound": {},
+ "caseIrrelevant": "Case Irrelevant",
+ "@caseIrrelevant": {},
+ "matchingAlgorithm": "Matching Algorithm",
+ "@matchingAlgorithm": {},
+ "match": "Match",
+ "@match": {},
+ "name": "Name",
+ "@name": {},
+ "notAssigned": "Not assigned",
+ "@notAssigned": {},
+ "addNewCorrespondent": "Add new correspondent",
+ "@addNewCorrespondent": {},
+ "noCorrespondentsSetUp": "You don't seem to have any correspondents set up.",
+ "@noCorrespondentsSetUp": {},
+ "correspondents": "Correspondents",
+ "@correspondents": {},
+ "addNewDocumentType": "Add new document type",
+ "@addNewDocumentType": {},
+ "noDocumentTypesSetUp": "You don't seem to have any document types set up.",
+ "@noDocumentTypesSetUp": {},
+ "documentTypes": "Document Types",
+ "@documentTypes": {},
+ "addNewStoragePath": "Add new storage path",
+ "@addNewStoragePath": {},
+ "noStoragePathsSetUp": "You don't seem to have any storage paths set up.",
+ "@noStoragePathsSetUp": {},
+ "storagePaths": "Storage Paths",
+ "@storagePaths": {},
+ "addNewTag": "Add new tag",
+ "@addNewTag": {},
+ "noTagsSetUp": "You don't seem to have any tags set up.",
+ "@noTagsSetUp": {},
+ "linkedDocuments": "Linked Documents",
+ "@linkedDocuments": {},
+ "advancedSettings": "Advanced Settings",
+ "@advancedSettings": {},
+ "passphrase": "Passphrase",
+ "@passphrase": {},
+ "configureMutualTLSAuthentication": "Configure Mutual TLS Authentication",
+ "@configureMutualTLSAuthentication": {},
+ "invalidCertificateFormat": "Invalid certificate format, only .pfx is allowed",
+ "@invalidCertificateFormat": {},
+ "clientcertificate": "Client Certificate",
+ "@clientcertificate": {},
+ "selectFile": "Select file...",
+ "@selectFile": {},
+ "continueLabel": "Continue",
+ "@continueLabel": {},
+ "incorrectOrMissingCertificatePassphrase": "Incorrect or missing certificate passphrase.",
+ "@incorrectOrMissingCertificatePassphrase": {},
+ "connect": "Connect",
+ "@connect": {},
+ "password": "Password",
+ "@password": {},
+ "passwordMustNotBeEmpty": "Password must not be empty.",
+ "@passwordMustNotBeEmpty": {},
+ "connectionTimedOut": "Connection timed out.",
+ "@connectionTimedOut": {},
+ "loginPageReachabilityMissingClientCertificateText": "A client certificate was expected but not sent. Please provide a certificate.",
+ "@loginPageReachabilityMissingClientCertificateText": {},
+ "couldNotEstablishConnectionToTheServer": "Could not establish a connection to the server.",
+ "@couldNotEstablishConnectionToTheServer": {},
+ "connectionSuccessfulylEstablished": "Connection successfully established.",
+ "@connectionSuccessfulylEstablished": {},
+ "hostCouldNotBeResolved": "Host could not be resolved. Please check the server address and your internet connection. ",
+ "@hostCouldNotBeResolved": {},
+ "serverAddress": "Server Address",
+ "@serverAddress": {},
+ "invalidAddress": "Invalid address.",
+ "@invalidAddress": {},
+ "serverAddressMustIncludeAScheme": "Server address must include a scheme.",
+ "@serverAddressMustIncludeAScheme": {},
+ "serverAddressMustNotBeEmpty": "Server address must not be empty.",
+ "@serverAddressMustNotBeEmpty": {},
+ "signIn": "Sign In",
+ "@signIn": {},
+ "loginPageSignInTitle": "Sign In",
+ "@loginPageSignInTitle": {},
+ "signInToServer": "Sign in to {serverAddress}",
+ "@signInToServer": {
+ "placeholders": {
+ "serverAddress": {}
+ }
+ },
+ "connectToPaperless": "Connect to Paperless",
+ "@connectToPaperless": {},
+ "username": "Username",
+ "@username": {},
+ "usernameMustNotBeEmpty": "Username must not be empty.",
+ "@usernameMustNotBeEmpty": {},
+ "documentContainsAllOfTheseWords": "Document contains all of these words",
+ "@documentContainsAllOfTheseWords": {},
+ "all": "All",
+ "@all": {},
+ "documentContainsAnyOfTheseWords": "Document contains any of these words",
+ "@documentContainsAnyOfTheseWords": {},
+ "any": "Any",
+ "@any": {},
+ "learnMatchingAutomatically": "Learn matching automatically",
+ "@learnMatchingAutomatically": {},
+ "auto": "Auto",
+ "@auto": {},
+ "documentContainsThisString": "Document contains this string",
+ "@documentContainsThisString": {},
+ "exact": "Exact",
+ "@exact": {},
+ "documentContainsAWordSimilarToThisWord": "Document contains a word similar to this word",
+ "@documentContainsAWordSimilarToThisWord": {},
+ "fuzzy": "Fuzzy",
+ "@fuzzy": {},
+ "documentMatchesThisRegularExpression": "Document matches this regular expression",
+ "@documentMatchesThisRegularExpression": {},
+ "regularExpression": "Regular Expression",
+ "@regularExpression": {},
+ "anInternetConnectionCouldNotBeEstablished": "An internet connection could not be established.",
+ "@anInternetConnectionCouldNotBeEstablished": {},
+ "done": "Done",
+ "@done": {},
+ "next": "Next",
+ "@next": {},
+ "couldNotAccessReceivedFile": "Could not access the received file. Please try to open the app before sharing.",
+ "@couldNotAccessReceivedFile": {},
+ "newView": "New View",
+ "@newView": {},
+ "createsASavedViewBasedOnTheCurrentFilterCriteria": "Creates a new view based on the current filter criteria.",
+ "@createsASavedViewBasedOnTheCurrentFilterCriteria": {},
+ "createViewsToQuicklyFilterYourDocuments": "Create views to quickly filter your documents.",
+ "@createViewsToQuicklyFilterYourDocuments": {},
+ "nFiltersSet": "{count, plural, zero{{count} filters set} one{{count} filter set} other{{count} filters set}}",
+ "@nFiltersSet": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "showInSidebar": "Show in sidebar",
+ "@showInSidebar": {},
+ "showOnDashboard": "Show on dashboard",
+ "@showOnDashboard": {},
+ "views": "Views",
+ "@views": {},
+ "clearAll": "Clear all",
+ "@clearAll": {},
+ "scan": "Scan",
+ "@scan": {},
+ "previewScan": "Preview",
+ "@previewScan": {},
+ "scrollToTop": "Scroll to top",
+ "@scrollToTop": {},
+ "paperlessServerVersion": "Paperless server version",
+ "@paperlessServerVersion": {},
+ "darkTheme": "Dark Theme",
+ "@darkTheme": {},
+ "lightTheme": "Light Theme",
+ "@lightTheme": {},
+ "systemTheme": "Use system theme",
+ "@systemTheme": {},
+ "appearance": "Appearance",
+ "@appearance": {},
+ "languageAndVisualAppearance": "Language and visual appearance",
+ "@languageAndVisualAppearance": {},
+ "applicationSettings": "Application",
+ "@applicationSettings": {},
+ "colorSchemeHint": "Choose between a classic color scheme inspired by a traditional Paperless green or use the dynamic color scheme based on your system theme.",
+ "@colorSchemeHint": {},
+ "colorSchemeNotSupportedWarning": "Dynamic theming is only supported for devices running Android 12 and above. Selecting the 'Dynamic' option might not have any effect depending on your OS implementation.",
+ "@colorSchemeNotSupportedWarning": {},
+ "colors": "Colors",
+ "@colors": {},
+ "language": "Language",
+ "@language": {},
+ "security": "Security",
+ "@security": {},
+ "mangeFilesAndStorageSpace": "Manage files and storage space",
+ "@mangeFilesAndStorageSpace": {},
+ "storage": "Storage",
+ "@storage": {},
+ "dark": "Dark",
+ "@dark": {},
+ "light": "Light",
+ "@light": {},
+ "system": "System",
+ "@system": {},
+ "ascending": "Ascending",
+ "@ascending": {},
+ "descending": "Descending",
+ "@descending": {},
+ "storagePathDay": "day",
+ "@storagePathDay": {},
+ "storagePathMonth": "month",
+ "@storagePathMonth": {},
+ "storagePathYear": "year",
+ "@storagePathYear": {},
+ "color": "Color",
+ "@color": {},
+ "filterTags": "Filter tags...",
+ "@filterTags": {},
+ "inboxTag": "Inbox-Tag",
+ "@inboxTag": {},
+ "uploadInferValuesHint": "If you specify values for these fields, your paperless instance will not automatically derive a value. If you want these values to be automatically populated by your server, leave the fields blank.",
+ "@uploadInferValuesHint": {},
+ "useTheConfiguredBiometricFactorToAuthenticate": "Use the configured biometric factor to authenticate and unlock your documents.",
+ "@useTheConfiguredBiometricFactorToAuthenticate": {},
+ "verifyYourIdentity": "Verify your identity",
+ "@verifyYourIdentity": {},
+ "verifyIdentity": "Verify Identity",
+ "@verifyIdentity": {},
+ "detailed": "Detailed",
+ "@detailed": {},
+ "grid": "Grid",
+ "@grid": {},
+ "list": "List",
+ "@list": {},
+ "remove": "Remove",
+ "removeQueryFromSearchHistory": "Remove query from search history?",
+ "dynamicColorScheme": "Dynamic",
+ "@dynamicColorScheme": {},
+ "classicColorScheme": "Classic",
+ "@classicColorScheme": {},
+ "notificationDownloadComplete": "Download complete",
+ "@notificationDownloadComplete": {
+ "description": "Notification title when a download has been completed."
+ },
+ "notificationDownloadingDocument": "Downloading document",
+ "@notificationDownloadingDocument": {
+ "description": "Notification title shown when a document download is pending"
+ },
+ "archiveSerialNumberUpdated": "Archive Serial Number updated.",
+ "@archiveSerialNumberUpdated": {
+ "description": "Message shown when the ASN has been updated."
+ },
+ "donateCoffee": "Buy me a coffee",
+ "@donateCoffee": {
+ "description": "Label displayed in the app drawer"
+ },
+ "thisFieldIsRequired": "This field is required!",
+ "@thisFieldIsRequired": {
+ "description": "Message shown below the form field when a required field has not been filled out."
+ },
+ "confirm": "Confirm",
+ "confirmAction": "Confirm action",
+ "@confirmAction": {
+ "description": "Typically used as a title to confirm a previously selected action"
+ },
+ "areYouSureYouWantToContinue": "Are you sure you want to continue?",
+ "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."
+ },
+ "bulkEditTagsRemoveMessage": "{count, plural, one{This operation will remove the tags {tags} from the selected document.} other{This operation will remove the tags {tags} from {count} selected documents.}}",
+ "@bulkEditTagsRemoveMessage": {
+ "description": "Message of the confirmation dialog when bulk removing tags."
+ },
+ "bulkEditTagsModifyMessage": "{count, plural, one{This operation will add the tags {addTags} and remove the tags {removeTags} from the selected document.} other{This operation will add the tags {addTags} and remove the tags {removeTags} from {count} selected documents.}}",
+ "@bulkEditTagsModifyMessage": {
+ "description": "Message of the confirmation dialog when both adding and removing tags."
+ },
+ "bulkEditCorrespondentAssignMessage": "{count, plural, one{This operation will assign the correspondent {correspondent} to the selected document.} other{This operation will assign the correspondent {correspondent} to {count} selected documents.}}",
+ "bulkEditDocumentTypeAssignMessage": "{count, plural, one{This operation will assign the document type {docType} to the selected document.} other{This operation will assign the documentType {docType} to {count} selected documents.}}",
+ "bulkEditStoragePathAssignMessage": "{count, plural, one{This operation will assign the storage path {path} to the selected document.} other{This operation will assign the storage path {path} to {count} selected documents.}}",
+ "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": {
+ "description": "Label shown when any tag should be filtered"
+ },
+ "allTags": "All",
+ "@allTags": {
+ "description": "Label shown when a document has to be assigned to all selected tags"
+ },
+ "switchingAccountsPleaseWait": "Switching accounts. Please wait...",
+ "@switchingAccountsPleaseWait": {
+ "description": "Message shown while switching accounts is in progress."
+ },
+ "testConnection": "Test connection",
+ "@testConnection": {
+ "description": "Button label shown on login page. Allows user to test whether the server is reachable or not."
+ },
+ "accounts": "Accounts",
+ "@accounts": {
+ "description": "Title of the account management dialog"
+ },
+ "addAccount": "Add account",
+ "@addAccount": {
+ "description": "Label of add account action"
+ },
+ "switchAccount": "Switch",
+ "@switchAccount": {
+ "description": "Label for switch account action"
+ },
+ "logout": "Logout",
+ "@logout": {
+ "description": "Generic Logout label"
+ },
+ "switchAccountTitle": "Switch account",
+ "@switchAccountTitle": {
+ "description": "Title of the dialog shown after adding an account, asking the user whether to switch to the newly added account or not."
+ },
+ "switchToNewAccount": "Do you want to switch to the new account? You can switch back at any time.",
+ "@switchToNewAccount": {
+ "description": "Content of the dialog shown after adding an account, asking the user whether to switch to the newly added account or not."
+ },
+ "sourceCode": "Source Code",
+ "findTheSourceCodeOn": "Find the source code on",
+ "@findTheSourceCodeOn": {
+ "description": "Text before link to Paperless Mobile GitHub"
+ },
+ "rememberDecision": "Remember my decision",
+ "defaultDownloadFileType": "Default Download File Type",
+ "@defaultDownloadFileType": {
+ "description": "Label indicating the default filetype to download (one of archived, original and always ask)"
+ },
+ "defaultShareFileType": "Default Share File Type",
+ "@defaultShareFileType": {
+ "description": "Label indicating the default filetype to share (one of archived, original and always ask)"
+ },
+ "alwaysAsk": "Always ask",
+ "@alwaysAsk": {
+ "description": "Option to choose when the app should always ask the user which filetype to use"
+ },
+ "disableMatching": "Do not tag documents automatically",
+ "@disableMatching": {
+ "description": "One of the options for automatic tagging of documents"
+ },
+ "none": "None",
+ "@none": {
+ "description": "One of available enum values of matching algorithm for tags"
+ },
+ "logInToExistingAccount": "Log in to existing account",
+ "@logInToExistingAccount": {
+ "description": "Title shown on login page if at least one user is already known to the app."
+ },
+ "print": "Print",
+ "@print": {
+ "description": "Tooltip for print button"
+ },
+ "managePermissions": "Manage permissions",
+ "@managePermissions": {
+ "description": "Button which leads user to manage permissions page"
+ },
+ "errorRetrievingServerVersion": "An error occurred trying to resolve the server version.",
+ "@errorRetrievingServerVersion": {
+ "description": "Message shown at the bottom of the settings page when the remote server version could not be resolved."
+ },
+ "resolvingServerVersion": "Resolving server version...",
+ "@resolvingServerVersion": {
+ "description": "Message shown while the app is loading the remote server version."
+ },
+ "goToLogin": "Go to login",
+ "@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": {
+ "description": "Label for button that exports scanned images to pdf (before upload)"
+ },
+ "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}",
+ "@invalidFilenameCharacter": {
+ "description": "For validating filename in export dialogue"
+ },
+ "exportScansToPdf": "Export scans to 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",
+ "@behavior": {
+ "description": "Title of the settings concerning app beahvior"
+ },
+ "theme": "Theme",
+ "@theme": {
+ "description": "Title of the theme mode setting"
+ },
+ "clearCache": "Clear cache",
+ "@clearCache": {
+ "description": "Title of the clear cache setting"
+ },
+ "freeBytes": "Free {byteString}",
+ "@freeBytes": {
+ "description": "Text shown for clear storage settings"
+ },
+ "calculatingDots": "Calculating...",
+ "@calculatingDots": {
+ "description": "Text shown when the byte size is still being calculated"
+ },
+ "freedDiskSpace": "Successfully freed {bytes} of disk space.",
+ "@freedDiskSpace": {
+ "description": "Message shown after clearing storage"
+ },
+ "uploadScansAsPdf": "Upload scans as 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": {
+ "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": {
+ "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."
+ },
+ "editView": "Edit View",
+ "@editView": {
+ "description": "Title of the edit saved view page"
+ },
+ "donate": "Donate",
+ "@donate": {
+ "description": "Label of the in-app donate button"
+ },
+ "donationDialogContent": "Thank you for considering to support this app! Due to both Google's and Apple's Payment Policies, no links leading to donations may be displayed in-app. Not even linking to the project's repository page appears to be allowed in this context. Therefore, maybe have a look at the 'Donations' section in the project's README. Your support is much appreciated and keeps the development of this app alive. Thanks!",
+ "@donationDialogContent": {
+ "description": "Text displayed in the donation dialog"
+ },
+ "noDocumentsFound": "No documents found.",
+ "@noDocumentsFound": {
+ "description": "Message shown when no documents were found."
+ },
+ "couldNotDeleteCorrespondent": "Could not delete correspondent, please try again.",
+ "@couldNotDeleteCorrespondent": {
+ "description": "Message shown in snackbar when a correspondent could not be deleted."
+ },
+ "couldNotDeleteDocumentType": "Could not delete document type, please try again.",
+ "@couldNotDeleteDocumentType": {
+ "description": "Message shown when a document type could not be deleted"
+ },
+ "couldNotDeleteTag": "Could not delete tag, please try again.",
+ "@couldNotDeleteTag": {
+ "description": "Message shown when a tag could not be deleted"
+ },
+ "couldNotDeleteStoragePath": "Could not delete storage path, please try again.",
+ "@couldNotDeleteStoragePath": {
+ "description": "Message shown when a storage path could not be deleted"
+ },
+ "couldNotUpdateCorrespondent": "Could not update correspondent, please try again.",
+ "@couldNotUpdateCorrespondent": {
+ "description": "Message shown when a correspondent could not be updated"
+ },
+ "couldNotUpdateDocumentType": "Could not update document type, please try again.",
+ "@couldNotUpdateDocumentType": {
+ "description": "Message shown when a document type could not be updated"
+ },
+ "couldNotUpdateTag": "Could not update tag, please try again.",
+ "@couldNotUpdateTag": {
+ "description": "Message shown when a tag could not be updated"
+ },
+ "couldNotLoadServerInformation": "Could not load server information.",
+ "@couldNotLoadServerInformation": {
+ "description": "Message shown when the server information could not be loaded"
+ },
+ "couldNotLoadStatistics": "Could not load server statistics.",
+ "@couldNotLoadStatistics": {
+ "description": "Message shown when the server statistics could not be loaded"
+ },
+ "couldNotLoadUISettings": "Could not load UI settings.",
+ "@couldNotLoadUISettings": {
+ "description": "Message shown when the UI settings could not be loaded"
+ },
+ "couldNotLoadTasks": "Could not load tasks.",
+ "@couldNotLoadTasks": {
+ "description": "Message shown when the tasks (e.g. document consumed) could not be loaded"
+ },
+ "userNotFound": "User could not be found.",
+ "@userNotFound": {
+ "description": "Message shown when the specified user (e.g. by id) could not be found"
+ },
+ "couldNotUpdateSavedView": "Could not update saved view, please try again.",
+ "@couldNotUpdateSavedView": {
+ "description": "Message shown when a saved view could not be updated"
+ },
+ "couldNotUpdateStoragePath": "Could not update storage path, please try again.",
+ "savedViewSuccessfullyUpdated": "Saved view successfully updated.",
+ "@savedViewSuccessfullyUpdated": {
+ "description": "Message shown when a saved view was successfully updated."
+ },
+ "discardChanges": "Discard changes?",
+ "@discardChanges": {
+ "description": "Title of the alert dialog shown when a user tries to close a view with unsaved changes."
+ },
+ "savedViewChangedDialogContent": "The filter conditions of the active view have changed. By resetting the filter, these changes will be lost. Do you still wish to continue?",
+ "@savedViewChangedDialogContent": {
+ "description": "Content of the alert dialog shown when all of the following applies:\r\n* User has saved view selected\r\n* User has performed changes to the current document filter\r\n* User now tries to reset this filter without having saved the changes to the view."
+ },
+ "createFromCurrentFilter": "Create from current filter",
+ "@createFromCurrentFilter": {
+ "description": "Tooltip of the \"New saved view\" button"
+ },
+ "home": "Home",
+ "@home": {
+ "description": "Label of the \"Home\" route"
+ },
+ "welcomeUser": "Welcome, {name}!",
+ "@welcomeUser": {
+ "description": "Top message shown on the home page"
+ },
+ "statistics": "Statistics",
+ "documentsInInbox": "Documents in inbox",
+ "totalDocuments": "Total documents",
+ "totalCharacters": "Total characters",
+ "showAll": "Show all",
+ "@showAll": {
+ "description": "Button label shown on a saved view preview to open this view in the documents page"
+ },
+ "userAlreadyExists": "This user already exists.",
+ "@userAlreadyExists": {
+ "description": "Error message shown when the user tries to add an already existing account."
+ },
+ "youDidNotSaveAnyViewsYet": "You did not save any views yet, create one and it will be shown here.",
+ "@youDidNotSaveAnyViewsYet": {
+ "description": "Message shown when there are no saved views yet."
+ },
+ "tryAgain": "Try again",
+ "discardFile": "Discard file?",
+ "discard": "Discard",
+ "backToLogin": "Back to login",
+ "skipEditingReceivedFiles": "Skip editing received files",
+ "uploadWithoutPromptingUploadForm": "Always upload without prompting the upload form when sharing files with the app.",
+ "authenticatingDots": "Authenticating...",
+ "@authenticatingDots": {
+ "description": "Message shown when the app is authenticating the user"
+ },
+ "persistingUserInformation": "Persisting user information...",
+ "fetchingUserInformation": "Fetching user information...",
+ "@fetchingUserInformation": {
+ "description": "Message shown when the app loads user data from the server"
+ },
+ "restoringSession": "Restoring session...",
+ "@restoringSession": {
+ "description": "Message shown when the user opens the app and the previous user is tried to be authenticated and logged in"
+ },
+ "documentsAssigned": "{count, plural, zero{No documents} one{1 document} other{{count} documents}}",
+ "@documentsAssigned": {
+ "description": "Text shown with a correspondent, document type etc. to indicate the number of documents this filter will maximally yield."
+ },
+ "discardChangesWarning": "You have unsaved changes. By continuing, all changes will be lost. Do you want to discard these changes?",
+ "@discardChangesWarning": {
+ "description": "Warning message shown when the user tries to close a route without saving the changes."
+ },
+ "changelog": "Changelog",
+ "noLogsFoundOn": "No logs found on {date}.",
+ "logfileBottomReached": "You have reached the bottom of this logfile.",
+ "appLogs": "App logs {date}",
+ "saveLogsToFile": "Save logs to file",
+ "copyToClipboard": "Copy to clipboard",
+ "couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
+ "loadingLogsFrom": "Loading logs from {date}...",
+ "clearLogs": "Clear logs from {date}",
+ "showPdf": "Show PDF",
+ "@showPdf": {
+ "description": "Tooltip shown on the \"show pdf\" button on the document edit page"
+ },
+ "hidePdf": "Hide PDF",
+ "@hidePdf": {
+ "description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
+ },
+ "misc": "Miscellaneous",
+ "loggingOut": "Logging out...",
+ "testingConnection": "Testing connection...",
+ "@testingConnection": {
+ "description": "Text shown while the app tries to establish a connection to the specified host."
+ },
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
+}
\ No newline at end of file
diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb
index 940d32a..cb7997b 100644
--- a/lib/l10n/intl_ru.arb
+++ b/lib/l10n/intl_ru.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb
index 98e3675..3f3ef00 100644
--- a/lib/l10n/intl_tr.arb
+++ b/lib/l10n/intl_tr.arb
@@ -1024,5 +1024,25 @@
"@testingConnection": {
"description": "Text shown while the app tries to establish a connection to the specified host."
},
- "version": "Version {versionCode}"
+ "version": "Version {versionCode}",
+ "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}",
+ "addNote": "Add note",
+ "newerVersionAvailable": "Newer version available:",
+ "dateOutOfRange": "Date must be between {firstDate} and {lastDate}.",
+ "@dateOutOfRange": {
+ "description": "Error message shown when the user tries to select a date outside of the allowed range.",
+ "placeholders": {
+ "firstDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ },
+ "lastDate": {
+ "type": "DateTime",
+ "format": "yMd"
+ }
+ }
+ },
+ "permissions": "Permissions",
+ "newNote": "New note",
+ "notesMarkdownSyntaxSupportHint": "Paperless Mobile can render notes using basic markdown syntax. Try it out!"
}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index d75ad3e..55c26fc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -24,6 +24,8 @@ import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/my_bloc_observer.dart';
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
+import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
+import 'package:paperless_mobile/core/database/hive/hive_initialization.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
@@ -31,7 +33,7 @@ import 'package:paperless_mobile/core/exception/server_message_exception.dart';
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
-import 'package:paperless_mobile/core/notifier/go_router_refresh_stream.dart';
+import 'package:paperless_mobile/core/security/session_manager_impl.dart';
import 'package:paperless_mobile/features/logging/data/formatted_printer.dart';
import 'package:paperless_mobile/features/logging/data/logger.dart';
import 'package:paperless_mobile/features/logging/data/mirrored_file_output.dart';
@@ -105,64 +107,36 @@ Future performMigrations() async {
}
}
-Future _initHive() async {
- await Hive.initFlutter();
- await performMigrations();
- registerHiveAdapters();
- await Hive.openBox(HiveBoxes.localUserAccount);
- await Hive.openBox(HiveBoxes.localUserAppState);
- await Hive.openBox(HiveBoxes.hosts);
- final globalSettingsBox =
- await Hive.openBox(HiveBoxes.globalSettings);
+Future initializeDefaultParameters() async {
+ Bloc.observer = MyBlocObserver();
+ await FileService.instance.initialize();
+ logger = l.Logger(
+ output: MirroredFileOutput(),
+ printer: FormattedPrinter(),
+ level: l.Level.trace,
+ filter: l.ProductionFilter(),
+ );
- if (!globalSettingsBox.hasValue) {
- await globalSettingsBox.setValue(
- GlobalSettings(preferredLocaleSubtag: defaultPreferredLocale.toString()),
- );
+ packageInfo = await PackageInfo.fromPlatform();
+
+ if (Platform.isAndroid) {
+ androidInfo = await DeviceInfoPlugin().androidInfo;
}
+ if (Platform.isIOS) {
+ iosInfo = await DeviceInfoPlugin().iosInfo;
+ }
+
+ await findSystemLocale();
}
void main() async {
runZonedGuarded(() async {
- Bloc.observer = MyBlocObserver();
- WidgetsFlutterBinding.ensureInitialized();
- await FileService.instance.initialize();
-
- logger = l.Logger(
- output: MirroredFileOutput(),
- printer: FormattedPrinter(),
- level: l.Level.trace,
- filter: l.ProductionFilter(),
- );
- Paint.enableDithering = true;
-
- // if (kDebugMode) {
- // // URL: http://localhost:3131
- // // Login: admin:test
- // await LocalMockApiServer(
- // // RandomDelayGenerator(
- // // const Duration(milliseconds: 100),
- // // const Duration(milliseconds: 800),
- // // ),
- // )
- // .start();
- // }
-
- packageInfo = await PackageInfo.fromPlatform();
-
- if (Platform.isAndroid) {
- androidInfo = await DeviceInfoPlugin().androidInfo;
- }
- if (Platform.isIOS) {
- iosInfo = await DeviceInfoPlugin().iosInfo;
- }
- await _initHive();
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
- final globalSettingsBox =
- Hive.box(HiveBoxes.globalSettings);
- final globalSettings = globalSettingsBox.getValue()!;
-
- await findSystemLocale();
+ final hiveDirectory = await getApplicationDocumentsDirectory();
+ final defaultLocale = defaultPreferredLocale.languageCode;
+ await initializeDefaultParameters();
+ await initHive(hiveDirectory, defaultLocale);
+ await performMigrations();
final connectivityStatusService = ConnectivityStatusServiceImpl(
Connectivity(),
@@ -178,10 +152,10 @@ void main() async {
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
final languageHeaderInterceptor = LanguageHeaderInterceptor(
- globalSettings.preferredLocaleSubtag,
+ () => Hive.globalSettingsBox.getValue()!.preferredLocaleSubtag,
);
// Manages security context, required for self signed client certificates
- final sessionManager = SessionManager([
+ final SessionManager sessionManager = SessionManagerImpl([
PrettyDioLogger(
compact: true,
responseBody: false,
@@ -194,21 +168,9 @@ void main() async {
languageHeaderInterceptor,
]);
- // Initialize Blocs/Cubits
- final connectivityCubit = ConnectivityCubit(connectivityStatusService);
-
- // Load application settings and stored authentication data
- await connectivityCubit.initialize();
-
final localNotificationService = LocalNotificationService();
await localNotificationService.initialize();
- //Update language header in interceptor on language change.
- globalSettingsBox.listenable().addListener(() {
- languageHeaderInterceptor.preferredLocaleSubtag =
- globalSettings.preferredLocaleSubtag;
- });
-
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
final authenticationCubit = AuthenticationCubit(
localAuthService,
@@ -218,33 +180,19 @@ void main() async {
localNotificationService,
);
runApp(
- MultiProvider(
- providers: [
- ChangeNotifierProvider.value(value: sessionManager),
- Provider.value(value: localAuthService),
- Provider.value(
- value: connectivityStatusService),
- Provider.value(
- value: localNotificationService),
- Provider.value(value: DocumentChangedNotifier()),
- ],
- child: MultiProvider(
- providers: [
- Provider.value(value: connectivityCubit),
- Provider.value(value: authenticationCubit),
- ],
- child: GoRouterShell(
- apiFactory: apiFactory,
- ),
- ),
+ AppEntrypoint(
+ sessionManager: sessionManager,
+ apiFactory: apiFactory,
+ authenticationCubit: authenticationCubit,
+ connectivityStatusService: connectivityStatusService,
+ localNotificationService: localNotificationService,
+ localAuthService: localAuthService,
),
);
}, (error, stackTrace) {
if (error is StateError &&
error.message.contains("Cannot emit new states")) {
- {
- return;
- }
+ return;
}
// Catches all unexpected/uncaught errors and prints them to the console.
final message = switch (error) {
@@ -261,9 +209,52 @@ void main() async {
});
}
+class AppEntrypoint extends StatelessWidget {
+ final PaperlessApiFactory apiFactory;
+ final AuthenticationCubit authenticationCubit;
+ final ConnectivityStatusService connectivityStatusService;
+ final LocalNotificationService localNotificationService;
+ final LocalAuthenticationService localAuthService;
+ final SessionManager sessionManager;
+
+ const AppEntrypoint({
+ super.key,
+ required this.apiFactory,
+ required this.authenticationCubit,
+ required this.connectivityStatusService,
+ required this.localNotificationService,
+ required this.localAuthService,
+ required this.sessionManager,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return MultiProvider(
+ providers: [
+ Provider.value(value: DocumentChangedNotifier()),
+ Provider.value(value: authenticationCubit),
+ Provider.value(
+ value: ConnectivityCubit(connectivityStatusService)..initialize(),
+ ),
+ ChangeNotifierProvider.value(value: sessionManager),
+ Provider.value(value: connectivityStatusService),
+ Provider.value(value: localNotificationService),
+ Provider.value(value: localAuthService),
+ ],
+ child: GoRouterShell(
+ apiFactory: apiFactory,
+ ),
+ );
+ }
+}
+
class GoRouterShell extends StatefulWidget {
final PaperlessApiFactory apiFactory;
- const GoRouterShell({super.key, required this.apiFactory});
+
+ const GoRouterShell({
+ super.key,
+ required this.apiFactory,
+ });
@override
State createState() => _GoRouterShellState();
@@ -396,7 +387,7 @@ class _GoRouterShellState extends State {
dynamicScheme: darkDynamic,
preferredColorScheme: settings.preferredColorSchemeOption,
),
- themeMode: settings.preferredThemeMode,
+ themeMode: settings.preferredThemeMode,
supportedLocales: const [
Locale('en'),
Locale('de'),
@@ -408,6 +399,7 @@ class _GoRouterShellState extends State {
Locale('pl'),
Locale('ru'),
Locale('tr'),
+ Locale('it'),
],
localeResolutionCallback: (locale, supportedLocales) {
if (locale == null) {
diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart
index 7f48599..3a44fa1 100644
--- a/lib/routing/routes.dart
+++ b/lib/routing/routes.dart
@@ -26,4 +26,5 @@ class R {
static const loggingOut = "loggingOut";
static const restoringSession = "restoringSession";
static const addAccount = 'addAccount';
+ static const addNote = 'addNote';
}
diff --git a/lib/routing/routes/login_route.dart b/lib/routing/routes/login_route.dart
index 835ba58..96c5547 100644
--- a/lib/routing/routes/login_route.dart
+++ b/lib/routing/routes/login_route.dart
@@ -12,6 +12,7 @@ import 'package:paperless_mobile/features/login/view/login_to_existing_account_p
import 'package:paperless_mobile/features/login/view/verify_identity_page.dart';
import 'package:paperless_mobile/features/login/view/widgets/login_transition_page.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
+import 'package:paperless_mobile/keys.dart';
import 'package:paperless_mobile/routing/navigation_keys.dart';
import 'package:paperless_mobile/routing/routes.dart';
part 'login_route.g.dart';
@@ -108,6 +109,7 @@ class AuthenticatingRoute extends GoRouteData {
};
return NoTransitionPage(
child: LoginTransitionPage(
+ key: TestKeys.login.loggingInScreen,
text: text,
),
);
diff --git a/lib/routing/routes/shells/authenticated_route.dart b/lib/routing/routes/shells/authenticated_route.dart
index feed381..9c0f585 100644
--- a/lib/routing/routes/shells/authenticated_route.dart
+++ b/lib/routing/routes/shells/authenticated_route.dart
@@ -71,6 +71,7 @@ part 'authenticated_route.g.dart';
TypedGoRoute(
path: "details/:id",
name: R.documentDetails,
+ routes: [],
),
TypedGoRoute(
path: "edit",
diff --git a/packages/paperless_api/lib/paperless_api.dart b/packages/paperless_api/lib/paperless_api.dart
index 444403f..8552f8c 100644
--- a/packages/paperless_api/lib/paperless_api.dart
+++ b/packages/paperless_api/lib/paperless_api.dart
@@ -4,3 +4,4 @@ export 'src/models/models.dart';
export 'src/modules/modules.dart';
export 'src/converters/converters.dart';
export 'config/hive/hive_type_ids.dart';
+export 'src/interceptor/dio_http_error_interceptor.dart';
diff --git a/lib/core/interceptor/dio_http_error_interceptor.dart b/packages/paperless_api/lib/src/interceptor/dio_http_error_interceptor.dart
similarity index 100%
rename from lib/core/interceptor/dio_http_error_interceptor.dart
rename to packages/paperless_api/lib/src/interceptor/dio_http_error_interceptor.dart
diff --git a/packages/paperless_api/lib/src/models/document_filter.dart b/packages/paperless_api/lib/src/models/document_filter.dart
index fb9d8a9..a2dace3 100644
--- a/packages/paperless_api/lib/src/models/document_filter.dart
+++ b/packages/paperless_api/lib/src/models/document_filter.dart
@@ -125,8 +125,8 @@ class DocumentFilter extends Equatable {
return queryParams;
}
- @override
- String toString() => toQueryParameters().toString();
+ // @override
+ // String toString() => toQueryParameters().toString();
DocumentFilter copyWith({
int? pageSize,
@@ -249,9 +249,4 @@ class DocumentFilter extends Equatable {
moreLike,
selectedView,
];
-
- // factory DocumentFilter.fromJson(Map json) =>
- // _$DocumentFilterFromJson(json);
-
- // Map toJson() => _$DocumentFilterToJson(this);
}
diff --git a/packages/paperless_api/lib/src/models/document_model.dart b/packages/paperless_api/lib/src/models/document_model.dart
index 335d280..38df520 100644
--- a/packages/paperless_api/lib/src/models/document_model.dart
+++ b/packages/paperless_api/lib/src/models/document_model.dart
@@ -1,10 +1,10 @@
-// ignore_for_file: non_constant_identifier_names
-
import 'package:equatable/equatable.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/models/custom_field_model.dart';
+import 'package:paperless_api/src/models/note_model.dart';
import 'package:paperless_api/src/models/search_hit.dart';
part 'document_model.g.dart';
@@ -48,8 +48,9 @@ class DocumentModel extends Equatable {
final int? owner;
final bool? userCanChange;
+ final Iterable notes;
- // Only present if full_perms=true
+ /// Only present if full_perms=true
final Permissions? permissions;
final Iterable customFields;
@@ -72,6 +73,7 @@ class DocumentModel extends Equatable {
this.userCanChange,
this.permissions,
this.customFields = const [],
+ this.notes = const [],
});
factory DocumentModel.fromJson(Map json) =>
@@ -94,6 +96,9 @@ class DocumentModel extends Equatable {
String? archivedFileName,
int? Function()? owner,
bool? userCanChange,
+ Iterable? notes,
+ Permissions? permissions,
+ Iterable? customFields,
}) {
return DocumentModel(
id: id,
@@ -114,6 +119,9 @@ class DocumentModel extends Equatable {
archivedFileName: archivedFileName ?? this.archivedFileName,
owner: owner != null ? owner() : this.owner,
userCanChange: userCanChange ?? this.userCanChange,
+ customFields: customFields ?? this.customFields,
+ notes: notes ?? this.notes,
+ permissions: permissions ?? this.permissions,
);
}
@@ -134,5 +142,8 @@ class DocumentModel extends Equatable {
archivedFileName,
owner,
userCanChange,
+ customFields,
+ notes,
+ permissions,
];
}
diff --git a/packages/paperless_api/lib/src/models/filter_rule_model.dart b/packages/paperless_api/lib/src/models/filter_rule_model.dart
index f974351..1f84e55 100644
--- a/packages/paperless_api/lib/src/models/filter_rule_model.dart
+++ b/packages/paperless_api/lib/src/models/filter_rule_model.dart
@@ -82,7 +82,6 @@ class FilterRule with EquatableMixin {
assert(filter.tags is IdsTagsQuery);
return filter.copyWith(
tags: switch (filter.tags) {
- // TODO: Handle this case.
IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery(
include: [...i, int.parse(value!)],
exclude: e,
diff --git a/packages/paperless_api/lib/src/models/models.dart b/packages/paperless_api/lib/src/models/models.dart
index e5b8495..fbb67f8 100644
--- a/packages/paperless_api/lib/src/models/models.dart
+++ b/packages/paperless_api/lib/src/models/models.dart
@@ -28,3 +28,4 @@ export 'task/task.dart';
export 'task/task_status.dart';
export 'user_model.dart';
export 'exception/exceptions.dart';
+export 'note_model.dart' show NoteModel;
diff --git a/packages/paperless_api/lib/src/models/note_model.dart b/packages/paperless_api/lib/src/models/note_model.dart
new file mode 100644
index 0000000..e5ebc6e
--- /dev/null
+++ b/packages/paperless_api/lib/src/models/note_model.dart
@@ -0,0 +1,29 @@
+// ignore_for_file: invalid_annotation_target
+
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'note_model.freezed.dart';
+part 'note_model.g.dart';
+
+@freezed
+class NoteModel with _$NoteModel {
+ const factory NoteModel({
+ required int? id,
+ required String? note,
+ required DateTime? created,
+ required int? document,
+ @JsonKey(fromJson: parseNoteUserFromJson) required int? user,
+ }) = _NoteModel;
+
+ factory NoteModel.fromJson(Map json) =>
+ _$NoteModelFromJson(json);
+}
+
+int? parseNoteUserFromJson(dynamic json) {
+ if (json == null) return null;
+ if (json is Map) {
+ return json['id'];
+ } else if (json is int) {
+ return json;
+ }
+ return null;
+}
diff --git a/packages/paperless_api/lib/src/models/paperless_api_exception.dart b/packages/paperless_api/lib/src/models/paperless_api_exception.dart
index ad60cd0..fde8e10 100644
--- a/packages/paperless_api/lib/src/models/paperless_api_exception.dart
+++ b/packages/paperless_api/lib/src/models/paperless_api_exception.dart
@@ -11,7 +11,16 @@ class PaperlessApiException implements Exception {
this.httpStatusCode,
});
- const PaperlessApiException.unknown() : this(ErrorCode.unknown);
+ const PaperlessApiException.unknown({
+ String? details,
+ StackTrace? stackTrace,
+ int? httpStatusCode,
+ }) : this(
+ ErrorCode.unknown,
+ details: details,
+ stackTrace: stackTrace,
+ httpStatusCode: httpStatusCode,
+ );
@override
String toString() {
@@ -71,5 +80,7 @@ enum ErrorCode {
updateSavedViewError,
customFieldCreateFailed,
customFieldLoadFailed,
- customFieldDeleteFailed;
+ customFieldDeleteFailed,
+ deleteNoteFailed,
+ addNoteFailed;
}
diff --git a/packages/paperless_api/lib/src/models/paperless_server_information_model.dart b/packages/paperless_api/lib/src/models/paperless_server_information_model.dart
index 73bb983..e37409a 100644
--- a/packages/paperless_api/lib/src/models/paperless_server_information_model.dart
+++ b/packages/paperless_api/lib/src/models/paperless_server_information_model.dart
@@ -4,6 +4,7 @@ class PaperlessServerInformationModel {
static const String versionHeader = 'x-version';
static const String apiVersionHeader = 'x-api-version';
final String version;
+ final String latestVersion;
final int apiVersion;
final bool isUpdateAvailable;
@@ -11,9 +12,11 @@ class PaperlessServerInformationModel {
required this.version,
required this.apiVersion,
required this.isUpdateAvailable,
+ required this.latestVersion,
});
int compareToOtherVersion(String other) {
- return getExtendedVersionNumber(version).compareTo(getExtendedVersionNumber(other));
+ return getExtendedVersionNumber(version)
+ .compareTo(getExtendedVersionNumber(other));
}
}
diff --git a/packages/paperless_api/lib/src/models/query_parameters/date_range_queries/date_range_query.dart b/packages/paperless_api/lib/src/models/query_parameters/date_range_queries/date_range_query.dart
index 3396aa2..d232aba 100644
--- a/packages/paperless_api/lib/src/models/query_parameters/date_range_queries/date_range_query.dart
+++ b/packages/paperless_api/lib/src/models/query_parameters/date_range_queries/date_range_query.dart
@@ -11,7 +11,7 @@ import 'date_range_unit.dart';
part 'date_range_query.g.dart';
-sealed class DateRangeQuery {
+sealed class DateRangeQuery with EquatableMixin {
const DateRangeQuery();
Map toQueryParameter(DateRangeQueryField field);
@@ -28,10 +28,13 @@ class UnsetDateRangeQuery extends DateRangeQuery {
@override
bool matches(DateTime dt) => true;
+
+ @override
+ List