mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 16:07:52 -06:00
feat: Add login integration test (WIP), update notes feature
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
- New feature: Notes
|
||||||
|
- Several bugfixes
|
||||||
16
build.yaml
Normal file
16
build.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
include:
|
||||||
|
- pubspec.yaml
|
||||||
|
sources:
|
||||||
|
- assets/**
|
||||||
|
- lib/$lib$
|
||||||
|
- lib/**.dart
|
||||||
|
- test/**.dart
|
||||||
|
- integration_test/**.dart
|
||||||
|
|
||||||
|
builders:
|
||||||
|
mockito|mockBuilder:
|
||||||
|
generate_for:
|
||||||
|
- test/**.dart
|
||||||
|
- integration_test/**.dart
|
||||||
@@ -1,224 +1,130 @@
|
|||||||
// import 'package:flutter/material.dart';
|
import 'dart:io';
|
||||||
// import 'package:flutter_test/flutter_test.dart';
|
|
||||||
// import 'package:mockito/mockito.dart';
|
|
||||||
// import 'package:paperless_api/paperless_api.dart';
|
|
||||||
// import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|
||||||
// import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
|
||||||
// import 'package:paperless_mobile/core/store/local_vault.dart';
|
|
||||||
// import 'package:paperless_mobile/di_test_mocks.mocks.dart';
|
|
||||||
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
// import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
|
||||||
// import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|
||||||
|
|
||||||
// import 'src/framework.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:mockito/mockito.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/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/keys.dart';
|
||||||
|
import 'package:paperless_mobile/main.dart'
|
||||||
|
show initializeDefaultParameters, AppEntrypoint;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
// void main() async {
|
import 'src/mocks/mock_paperless_api.dart';
|
||||||
// final t = await initializeTestingFramework(languageCode: 'de');
|
|
||||||
|
|
||||||
// const testServerUrl = 'https://example.com';
|
class MockConnectivityStatusService extends Mock
|
||||||
// const testUsername = 'user';
|
implements ConnectivityStatusService {}
|
||||||
// const testPassword = 'pass';
|
|
||||||
|
|
||||||
// final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
class MockLocalAuthService extends Mock implements LocalAuthenticationService {}
|
||||||
// final usernameField = find.byKey(const ValueKey('login-username'));
|
|
||||||
// final passwordField = find.byKey(const ValueKey('login-password'));
|
|
||||||
// final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
|
||||||
|
|
||||||
// testWidgets('Test successful login flow', (WidgetTester tester) async {
|
class MockSessionManager extends Mock implements SessionManager {}
|
||||||
// await initAndLaunchTestApp(tester, () async {
|
|
||||||
// // Initialize dat for mocked classes
|
|
||||||
// when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
|
||||||
// .thenAnswer((i) => Stream.value(true));
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault)
|
|
||||||
// .loadAuthenticationInformation())
|
|
||||||
// .thenAnswer((realInvocation) async => null);
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
|
||||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
|
||||||
// preferredLocaleSubtag: 'en',
|
|
||||||
// preferredThemeMode: ThemeMode.light,
|
|
||||||
// isLocalAuthenticationEnabled: false,
|
|
||||||
// preferredViewType: ViewType.list,
|
|
||||||
// showInboxOnStartup: false,
|
|
||||||
// ));
|
|
||||||
// when(getIt<PaperlessAuthenticationApi>().login(
|
|
||||||
// username: testUsername,
|
|
||||||
// password: testPassword,
|
|
||||||
// )).thenAnswer((i) => Future.value("eyTestToken"));
|
|
||||||
|
|
||||||
// await getIt<ConnectivityCubit>().initialize();
|
class MockLocalNotificationService extends Mock
|
||||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
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;
|
final hiveDirectory = await getTemporaryDirectory();
|
||||||
// await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// await tester.enterText(serverAddressField, testServerUrl);
|
late ConnectivityStatusService connectivityStatusService;
|
||||||
// await tester.pumpAndSettle();
|
late MockPaperlessApiFactory paperlessApiFactory;
|
||||||
|
late AuthenticationCubit authenticationCubit;
|
||||||
|
late LocalNotificationService localNotificationService;
|
||||||
|
late SessionManager sessionManager;
|
||||||
|
final localAuthService = MockLocalAuthService();
|
||||||
|
|
||||||
// await tester.enterText(usernameField, testUsername);
|
setUp(() async {
|
||||||
// await tester.pumpAndSettle();
|
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 initializeDefaultParameters();
|
||||||
// await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// 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<PaperlessAuthenticationApi>().login(
|
await tester.enterText(
|
||||||
// username: testUsername,
|
find.byKey(TestKeys.login.serverAddressFormField),
|
||||||
// password: testPassword,
|
testServerUrl,
|
||||||
// )).called(1);
|
);
|
||||||
// });
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// testWidgets('Test login validation missing password',
|
await tester.press(find.byKey(TestKeys.login.continueButton));
|
||||||
// (WidgetTester tester) async {
|
|
||||||
// await initAndLaunchTestApp(tester, () async {
|
|
||||||
// when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
|
||||||
// .connectivityChanges())
|
|
||||||
// .thenAnswer((i) => Stream.value(true));
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault)
|
|
||||||
// .loadAuthenticationInformation())
|
|
||||||
// .thenAnswer((realInvocation) async => null);
|
|
||||||
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
await tester.pumpAndSettle();
|
||||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
expect(
|
||||||
// preferredLocaleSubtag: 'en',
|
find.byKey(TestKeys.login.usernameFormField),
|
||||||
// preferredThemeMode: ThemeMode.light,
|
findsOneWidget,
|
||||||
// isLocalAuthenticationEnabled: false,
|
);
|
||||||
// preferredViewType: ViewType.list,
|
|
||||||
// showInboxOnStartup: false,
|
|
||||||
// ));
|
|
||||||
|
|
||||||
// await getIt<ConnectivityCubit>().initialize();
|
await tester.enterText(
|
||||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
find.byKey(TestKeys.login.usernameFormField),
|
||||||
// });
|
testUsername,
|
||||||
// // Mocked classes
|
);
|
||||||
|
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;
|
expect(
|
||||||
// await tester.pumpAndSettle();
|
find.byKey(TestKeys.login.loggingInScreen),
|
||||||
|
findsOneWidget,
|
||||||
// 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<PaperlessAuthenticationApi>() 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<ConnectivityStatusService>() as MockConnectivityStatusService)
|
|
||||||
// .connectivityChanges())
|
|
||||||
// .thenAnswer((i) => Stream.value(true));
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault)
|
|
||||||
// .loadAuthenticationInformation())
|
|
||||||
// .thenAnswer((realInvocation) async => null);
|
|
||||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
|
||||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
|
||||||
// preferredLocaleSubtag: 'en',
|
|
||||||
// preferredThemeMode: ThemeMode.light,
|
|
||||||
// isLocalAuthenticationEnabled: false,
|
|
||||||
// preferredViewType: ViewType.list,
|
|
||||||
// showInboxOnStartup: false,
|
|
||||||
// ));
|
|
||||||
// await getIt<ConnectivityCubit>().initialize();
|
|
||||||
// await getIt<ApplicationSettingsCubit>().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<PaperlessAuthenticationApi>() 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<ConnectivityStatusService>()).connectivityChanges())
|
|
||||||
// .thenAnswer((i) => Stream.value(true));
|
|
||||||
|
|
||||||
// when((getIt<LocalVault>()).loadAuthenticationInformation())
|
|
||||||
// .thenAnswer((realInvocation) async => null);
|
|
||||||
|
|
||||||
// when((getIt<LocalVault>()).loadApplicationSettings())
|
|
||||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
|
||||||
// preferredLocaleSubtag: 'en',
|
|
||||||
// preferredThemeMode: ThemeMode.light,
|
|
||||||
// isLocalAuthenticationEnabled: false,
|
|
||||||
// preferredViewType: ViewType.list,
|
|
||||||
// showInboxOnStartup: false,
|
|
||||||
// ));
|
|
||||||
|
|
||||||
// await getIt<ConnectivityCubit>().initialize();
|
|
||||||
// await getIt<ApplicationSettingsCubit>().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<PaperlessAuthenticationApi>().login(
|
|
||||||
// username: testUsername,
|
|
||||||
// password: testPassword,
|
|
||||||
// ));
|
|
||||||
// expect(
|
|
||||||
// find.textContaining(
|
|
||||||
// t.translations.loginPageServerUrlValidatorMessageText),
|
|
||||||
// findsOneWidget,
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_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/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/main.dart'
|
||||||
|
show initializeDefaultParameters, AppEntrypoint;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
Future<TestingFrameworkVariables> initializeTestingFramework(
|
Future<TestingFrameworkVariables> initializeTestingFramework(
|
||||||
{String languageCode = 'en'}) async {
|
{String languageCode = 'en'}) async {
|
||||||
@@ -26,11 +35,3 @@ class TestingFrameworkVariables {
|
|||||||
required this.translations,
|
required this.translations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAndLaunchTestApp(
|
|
||||||
WidgetTester tester,
|
|
||||||
Future<void> Function() initializationCallback,
|
|
||||||
) async {
|
|
||||||
await initializationCallback();
|
|
||||||
//runApp(const PaperlessMobileEntrypoint(authenticationCubit: ),));
|
|
||||||
}
|
|
||||||
|
|||||||
65
integration_test/src/mocks/mock_paperless_api.dart
Normal file
65
integration_test/src/mocks/mock_paperless_api.dart
Normal file
@@ -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<PaperlessAuthenticationApi>(),
|
||||||
|
MockSpec<PaperlessDocumentsApi>(),
|
||||||
|
MockSpec<PaperlessLabelsApi>(),
|
||||||
|
MockSpec<PaperlessUserApi>(),
|
||||||
|
MockSpec<PaperlessServerStatsApi>(),
|
||||||
|
MockSpec<PaperlessSavedViewsApi>(),
|
||||||
|
MockSpec<PaperlessTasksApi>(),
|
||||||
|
])
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/core/database/hive/hive_initialization.dart
Normal file
25
lib/core/database/hive/hive_initialization.dart
Normal file
@@ -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<void> initHive(Directory directory, String defaultLocale) async {
|
||||||
|
Hive.init(directory.path);
|
||||||
|
registerHiveAdapters();
|
||||||
|
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
|
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||||
|
await Hive.openBox<bool>(HiveBoxes.hintStateBox);
|
||||||
|
await Hive.openBox<String>(HiveBoxes.hosts);
|
||||||
|
final globalSettingsBox =
|
||||||
|
await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
|
||||||
|
|
||||||
|
if (!globalSettingsBox.hasValue) {
|
||||||
|
await globalSettingsBox.setValue(
|
||||||
|
GlobalSettings(preferredLocaleSubtag: defaultLocale),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
class LanguageHeaderInterceptor extends Interceptor {
|
class LanguageHeaderInterceptor extends Interceptor {
|
||||||
String preferredLocaleSubtag;
|
final String Function() preferredLocaleSubtagBuilder;
|
||||||
LanguageHeaderInterceptor(this.preferredLocaleSubtag);
|
LanguageHeaderInterceptor(this.preferredLocaleSubtagBuilder);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
late String languages;
|
late String languages;
|
||||||
if (preferredLocaleSubtag == "en") {
|
if (preferredLocaleSubtagBuilder() == "en") {
|
||||||
languages = "en";
|
languages = "en";
|
||||||
} else {
|
} 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});
|
options.headers.addAll({"Accept-Language": languages});
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
|
|||||||
@@ -1,93 +1,14 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/src/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: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
|
abstract interface class SessionManager implements ChangeNotifier {
|
||||||
/// an underlying [Dio] client which is injected into all services
|
Dio get client;
|
||||||
/// requiring authenticated access to the Paperless REST API.
|
|
||||||
class SessionManager extends ValueNotifier<Dio> {
|
|
||||||
Dio get client => value;
|
|
||||||
|
|
||||||
SessionManager([List<Interceptor> interceptors = const []])
|
|
||||||
: super(_initDio(interceptors));
|
|
||||||
|
|
||||||
static Dio _initDio(List<Interceptor> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSettings({
|
void updateSettings({
|
||||||
String? baseUrl,
|
String? baseUrl,
|
||||||
String? authToken,
|
String? authToken,
|
||||||
ClientCertificate? clientCertificate,
|
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',
|
|
||||||
});
|
});
|
||||||
}
|
void resetSettings();
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetSettings() {
|
|
||||||
client.httpClientAdapter = IOHttpClientAdapter();
|
|
||||||
client.options.baseUrl = '';
|
|
||||||
client.options.headers.remove(HttpHeaders.authorizationHeader);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
lib/core/security/session_manager_impl.dart
Normal file
96
lib/core/security/session_manager_impl.dart
Normal file
@@ -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<Dio> implements SessionManager {
|
||||||
|
@override
|
||||||
|
Dio get client => value;
|
||||||
|
|
||||||
|
SessionManagerImpl([List<Interceptor> interceptors = const []])
|
||||||
|
: super(_initDio(interceptors));
|
||||||
|
|
||||||
|
static Dio _initDio(List<Interceptor> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:paperless_mobile/core/global/os_error_codes.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/interceptor/server_reachability_error_interceptor.dart';
|
||||||
import 'package:paperless_mobile/core/security/session_manager.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/client_certificate.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
@@ -79,7 +80,7 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
SessionManager manager =
|
SessionManager manager =
|
||||||
SessionManager([ServerReachabilityErrorInterceptor()])
|
SessionManagerImpl([ServerReachabilityErrorInterceptor()])
|
||||||
..updateSettings(clientCertificate: clientCertificate)
|
..updateSettings(clientCertificate: clientCertificate)
|
||||||
..client.options.connectTimeout = const Duration(seconds: 5)
|
..client.options.connectTimeout = const Duration(seconds: 5)
|
||||||
..client.options.receiveTimeout = const Duration(seconds: 5);
|
..client.options.receiveTimeout = const Duration(seconds: 5);
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ class _DocumentNotesWidgetState extends State<DocumentNotesWidget> {
|
|||||||
label: Text(S.of(context)!.addNote),
|
label: Text(S.of(context)!.addNote),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_formKey.currentState?.save();
|
_formKey.currentState?.save();
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isNoteSubmitting = true;
|
_isNoteSubmitting = true;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.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_config.dart';
|
||||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
@@ -13,6 +14,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/database/tables/user_credentials.dart';
|
||||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.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/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/data/logger.dart';
|
||||||
import 'package:paperless_mobile/features/logging/utils/redaction_utils.dart';
|
import 'package:paperless_mobile/features/logging/utils/redaction_utils.dart';
|
||||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||||
@@ -83,7 +85,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
AuthenticatingStage.persistingLocalUserData));
|
AuthenticatingStage.persistingLocalUserData));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} on PaperlessApiException catch (exception, stackTrace) {
|
||||||
emit(
|
emit(
|
||||||
AuthenticationErrorState(
|
AuthenticationErrorState(
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
@@ -207,8 +209,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
methodName: 'switchAccount',
|
methodName: 'switchAccount',
|
||||||
);
|
);
|
||||||
|
|
||||||
final sessionManager = SessionManager([
|
final SessionManager sessionManager = SessionManagerImpl([
|
||||||
LanguageHeaderInterceptor(locale),
|
LanguageHeaderInterceptor(() => locale),
|
||||||
]);
|
]);
|
||||||
await _addUser(
|
await _addUser(
|
||||||
localUserId,
|
localUserId,
|
||||||
@@ -462,14 +464,12 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
|
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
|
||||||
|
|
||||||
|
await onPerformLogin?.call();
|
||||||
logger.fd(
|
logger.fd(
|
||||||
"Fetching bearer token from the server...",
|
"Fetching bearer token from the server...",
|
||||||
className: runtimeType.toString(),
|
className: runtimeType.toString(),
|
||||||
methodName: '_addUser',
|
methodName: '_addUser',
|
||||||
);
|
);
|
||||||
|
|
||||||
await onPerformLogin?.call();
|
|
||||||
|
|
||||||
final token = await authApi.login(
|
final token = await authApi.login(
|
||||||
username: credentials.username!,
|
username: credentials.username!,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
@@ -486,7 +486,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
clientCertificate: clientCert,
|
clientCertificate: clientCert,
|
||||||
authToken: token,
|
authToken: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
final userAccountBox =
|
final userAccountBox =
|
||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
final userStateBox =
|
final userStateBox =
|
||||||
@@ -586,12 +585,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
clientCertificate: clientCert,
|
clientCertificate: clientCert,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.fd(
|
logger.fd(
|
||||||
"User credentials successfully saved.",
|
"User credentials successfully saved.",
|
||||||
className: runtimeType.toString(),
|
className: runtimeType.toString(),
|
||||||
methodName: '_addUser',
|
methodName: '_addUser',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final hostsBox = Hive.box<String>(HiveBoxes.hosts);
|
final hostsBox = Hive.box<String>(HiveBoxes.hosts);
|
||||||
if (!hostsBox.values.contains(serverUrl)) {
|
if (!hostsBox.values.contains(serverUrl)) {
|
||||||
await hostsBox.add(serverUrl);
|
await hostsBox.add(serverUrl);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.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/login_form_credentials.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/reachability_status.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/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/server_address_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_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';
|
import 'package:paperless_mobile/generated/assets.gen.dart';
|
||||||
@@ -44,6 +42,7 @@ class AddAccountPage extends StatefulWidget {
|
|||||||
final bool showLocalAccounts;
|
final bool showLocalAccounts;
|
||||||
|
|
||||||
final Widget? bottomLeftButton;
|
final Widget? bottomLeftButton;
|
||||||
|
|
||||||
const AddAccountPage({
|
const AddAccountPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onSubmit,
|
required this.onSubmit,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:hive_flutter/adapters.dart';
|
|||||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||||
|
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/keys.dart';
|
||||||
|
|
||||||
class ServerAddressFormField extends StatefulWidget {
|
class ServerAddressFormField extends StatefulWidget {
|
||||||
static const String fkServerAddress = "serverAddress";
|
static const String fkServerAddress = "serverAddress";
|
||||||
@@ -59,7 +60,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField>
|
|||||||
maxWidth: MediaQuery.sizeOf(context).width - 40,
|
maxWidth: MediaQuery.sizeOf(context).width - 40,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
key: const ValueKey('login-server-address'),
|
key: TestKeys.login.serverAddressFormField,
|
||||||
optionsBuilder: (textEditingValue) {
|
optionsBuilder: (textEditingValue) {
|
||||||
return Hive.box<String>(HiveBoxes.hosts)
|
return Hive.box<String>(HiveBoxes.hosts)
|
||||||
.values
|
.values
|
||||||
|
|||||||
19
lib/keys.dart
Normal file
19
lib/keys.dart
Normal file
@@ -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');
|
||||||
|
}
|
||||||
142
lib/main.dart
142
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/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/my_bloc_observer.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_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/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.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.dart';
|
||||||
import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.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/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/formatted_printer.dart';
|
||||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||||
import 'package:paperless_mobile/features/logging/data/mirrored_file_output.dart';
|
import 'package:paperless_mobile/features/logging/data/mirrored_file_output.dart';
|
||||||
@@ -105,49 +107,15 @@ Future<void> performMigrations() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initHive() async {
|
Future<void> initializeDefaultParameters() async {
|
||||||
await Hive.initFlutter();
|
|
||||||
await performMigrations();
|
|
||||||
registerHiveAdapters();
|
|
||||||
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
|
||||||
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
|
||||||
await Hive.openBox<bool>(HiveBoxes.hintStateBox);
|
|
||||||
await Hive.openBox<String>(HiveBoxes.hosts);
|
|
||||||
final globalSettingsBox =
|
|
||||||
await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
|
|
||||||
|
|
||||||
if (!globalSettingsBox.hasValue) {
|
|
||||||
await globalSettingsBox.setValue(
|
|
||||||
GlobalSettings(preferredLocaleSubtag: defaultPreferredLocale.toString()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
runZonedGuarded(() async {
|
|
||||||
Bloc.observer = MyBlocObserver();
|
Bloc.observer = MyBlocObserver();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await FileService.instance.initialize();
|
await FileService.instance.initialize();
|
||||||
|
|
||||||
logger = l.Logger(
|
logger = l.Logger(
|
||||||
output: MirroredFileOutput(),
|
output: MirroredFileOutput(),
|
||||||
printer: FormattedPrinter(),
|
printer: FormattedPrinter(),
|
||||||
level: l.Level.trace,
|
level: l.Level.trace,
|
||||||
filter: l.ProductionFilter(),
|
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();
|
packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
@@ -157,13 +125,18 @@ void main() async {
|
|||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
iosInfo = await DeviceInfoPlugin().iosInfo;
|
iosInfo = await DeviceInfoPlugin().iosInfo;
|
||||||
}
|
}
|
||||||
await _initHive();
|
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final globalSettingsBox =
|
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings);
|
|
||||||
final globalSettings = globalSettingsBox.getValue()!;
|
|
||||||
|
|
||||||
await findSystemLocale();
|
await findSystemLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
runZonedGuarded(() async {
|
||||||
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final hiveDirectory = await getApplicationDocumentsDirectory();
|
||||||
|
final defaultLocale = defaultPreferredLocale.languageCode;
|
||||||
|
await initializeDefaultParameters();
|
||||||
|
await initHive(hiveDirectory, defaultLocale);
|
||||||
|
await performMigrations();
|
||||||
|
|
||||||
final connectivityStatusService = ConnectivityStatusServiceImpl(
|
final connectivityStatusService = ConnectivityStatusServiceImpl(
|
||||||
Connectivity(),
|
Connectivity(),
|
||||||
@@ -179,10 +152,10 @@ void main() async {
|
|||||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||||
|
|
||||||
final languageHeaderInterceptor = LanguageHeaderInterceptor(
|
final languageHeaderInterceptor = LanguageHeaderInterceptor(
|
||||||
globalSettings.preferredLocaleSubtag,
|
() => Hive.globalSettingsBox.getValue()!.preferredLocaleSubtag,
|
||||||
);
|
);
|
||||||
// Manages security context, required for self signed client certificates
|
// Manages security context, required for self signed client certificates
|
||||||
final sessionManager = SessionManager([
|
final SessionManager sessionManager = SessionManagerImpl([
|
||||||
PrettyDioLogger(
|
PrettyDioLogger(
|
||||||
compact: true,
|
compact: true,
|
||||||
responseBody: false,
|
responseBody: false,
|
||||||
@@ -195,21 +168,9 @@ void main() async {
|
|||||||
languageHeaderInterceptor,
|
languageHeaderInterceptor,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Initialize Blocs/Cubits
|
|
||||||
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
|
||||||
|
|
||||||
// Load application settings and stored authentication data
|
|
||||||
await connectivityCubit.initialize();
|
|
||||||
|
|
||||||
final localNotificationService = LocalNotificationService();
|
final localNotificationService = LocalNotificationService();
|
||||||
await localNotificationService.initialize();
|
await localNotificationService.initialize();
|
||||||
|
|
||||||
//Update language header in interceptor on language change.
|
|
||||||
globalSettingsBox.listenable().addListener(() {
|
|
||||||
languageHeaderInterceptor.preferredLocaleSubtag =
|
|
||||||
globalSettings.preferredLocaleSubtag;
|
|
||||||
});
|
|
||||||
|
|
||||||
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
||||||
final authenticationCubit = AuthenticationCubit(
|
final authenticationCubit = AuthenticationCubit(
|
||||||
localAuthService,
|
localAuthService,
|
||||||
@@ -219,34 +180,20 @@ void main() async {
|
|||||||
localNotificationService,
|
localNotificationService,
|
||||||
);
|
);
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
AppEntrypoint(
|
||||||
providers: [
|
sessionManager: sessionManager,
|
||||||
ChangeNotifierProvider.value(value: sessionManager),
|
|
||||||
Provider<LocalAuthenticationService>.value(value: localAuthService),
|
|
||||||
Provider<ConnectivityStatusService>.value(
|
|
||||||
value: connectivityStatusService),
|
|
||||||
Provider<LocalNotificationService>.value(
|
|
||||||
value: localNotificationService),
|
|
||||||
Provider.value(value: DocumentChangedNotifier()),
|
|
||||||
],
|
|
||||||
child: MultiProvider(
|
|
||||||
providers: [
|
|
||||||
Provider<ConnectivityCubit>.value(value: connectivityCubit),
|
|
||||||
Provider.value(value: authenticationCubit),
|
|
||||||
],
|
|
||||||
child: GoRouterShell(
|
|
||||||
apiFactory: apiFactory,
|
apiFactory: apiFactory,
|
||||||
),
|
authenticationCubit: authenticationCubit,
|
||||||
),
|
connectivityStatusService: connectivityStatusService,
|
||||||
|
localNotificationService: localNotificationService,
|
||||||
|
localAuthService: localAuthService,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, (error, stackTrace) {
|
}, (error, stackTrace) {
|
||||||
if (error is StateError &&
|
if (error is StateError &&
|
||||||
error.message.contains("Cannot emit new states")) {
|
error.message.contains("Cannot emit new states")) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Catches all unexpected/uncaught errors and prints them to the console.
|
// Catches all unexpected/uncaught errors and prints them to the console.
|
||||||
final message = switch (error) {
|
final message = switch (error) {
|
||||||
PaperlessApiException e => e.details ?? error.toString(),
|
PaperlessApiException e => e.details ?? error.toString(),
|
||||||
@@ -262,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 {
|
class GoRouterShell extends StatefulWidget {
|
||||||
final PaperlessApiFactory apiFactory;
|
final PaperlessApiFactory apiFactory;
|
||||||
const GoRouterShell({super.key, required this.apiFactory});
|
|
||||||
|
const GoRouterShell({
|
||||||
|
super.key,
|
||||||
|
required this.apiFactory,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GoRouterShell> createState() => _GoRouterShellState();
|
State<GoRouterShell> createState() => _GoRouterShellState();
|
||||||
|
|||||||
@@ -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/verify_identity_page.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/login_transition_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/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/keys.dart';
|
||||||
import 'package:paperless_mobile/routing/navigation_keys.dart';
|
import 'package:paperless_mobile/routing/navigation_keys.dart';
|
||||||
import 'package:paperless_mobile/routing/routes.dart';
|
import 'package:paperless_mobile/routing/routes.dart';
|
||||||
part 'login_route.g.dart';
|
part 'login_route.g.dart';
|
||||||
@@ -108,6 +109,7 @@ class AuthenticatingRoute extends GoRouteData {
|
|||||||
};
|
};
|
||||||
return NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: LoginTransitionPage(
|
child: LoginTransitionPage(
|
||||||
|
key: TestKeys.login.loggingInScreen,
|
||||||
text: text,
|
text: text,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -82,14 +82,14 @@ SystemUiOverlayStyle buildOverlayStyle(
|
|||||||
Brightness.light => SystemUiOverlayStyle.dark.copyWith(
|
Brightness.light => SystemUiOverlayStyle.dark.copyWith(
|
||||||
systemNavigationBarColor: color,
|
systemNavigationBarColor: color,
|
||||||
systemNavigationBarDividerColor: color,
|
systemNavigationBarDividerColor: color,
|
||||||
// statusBarColor: theme.colorScheme.background,
|
statusBarColor: theme.colorScheme.background,
|
||||||
// statusBarColor: theme.colorScheme.background,
|
// statusBarColor: theme.colorScheme.background,
|
||||||
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
Brightness.dark => SystemUiOverlayStyle.light.copyWith(
|
Brightness.dark => SystemUiOverlayStyle.light.copyWith(
|
||||||
systemNavigationBarColor: color,
|
systemNavigationBarColor: color,
|
||||||
systemNavigationBarDividerColor: color,
|
systemNavigationBarDividerColor: color,
|
||||||
// statusBarColor: theme.colorScheme.background,
|
statusBarColor: theme.colorScheme.background,
|
||||||
// statusBarColor: theme.colorScheme.background,
|
// statusBarColor: theme.colorScheme.background,
|
||||||
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
// systemNavigationBarDividerColor: theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class DocumentModel extends Equatable {
|
|||||||
this.userCanChange,
|
this.userCanChange,
|
||||||
this.permissions,
|
this.permissions,
|
||||||
this.customFields = const [],
|
this.customFields = const [],
|
||||||
this.notes = const [] = const [],
|
this.notes = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DocumentModel.fromJson(Map<String, dynamic> json) =>
|
factory DocumentModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -101,10 +101,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.18.0"
|
||||||
colorfilter_generator:
|
colorfilter_generator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -244,10 +244,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
paperless_document_scanner:
|
paperless_document_scanner:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -376,18 +376,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
stream_transform:
|
stream_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -416,10 +416,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -440,10 +440,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.3.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -469,5 +469,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.0 <4.0.0"
|
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||||
flutter: ">=3.13.0"
|
flutter: ">=3.13.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user