mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 09:15:49 -06:00
WIP - Replaced get_it + injectable with Provider
This commit is contained in:
@@ -1,225 +1,224 @@
|
|||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
// import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
// import 'package:mockito/mockito.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
// import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
// import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
// import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
// import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
// import 'package:paperless_mobile/di_test_mocks.mocks.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/bloc/application_settings_cubit.dart';
|
// import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
// import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|
||||||
|
|
||||||
import 'src/framework.dart';
|
// import 'src/framework.dart';
|
||||||
|
|
||||||
void main() async {
|
// void main() async {
|
||||||
final t = await initializeTestingFramework(languageCode: 'de');
|
// final t = await initializeTestingFramework(languageCode: 'de');
|
||||||
|
|
||||||
const testServerUrl = 'https://example.com';
|
// const testServerUrl = 'https://example.com';
|
||||||
const testUsername = 'user';
|
// const testUsername = 'user';
|
||||||
const testPassword = 'pass';
|
// const testPassword = 'pass';
|
||||||
|
|
||||||
final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
// final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
||||||
final usernameField = find.byKey(const ValueKey('login-username'));
|
// final usernameField = find.byKey(const ValueKey('login-username'));
|
||||||
final passwordField = find.byKey(const ValueKey('login-password'));
|
// final passwordField = find.byKey(const ValueKey('login-password'));
|
||||||
final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
// final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
||||||
|
|
||||||
testWidgets('Test successful login flow', (WidgetTester tester) async {
|
// testWidgets('Test successful login flow', (WidgetTester tester) async {
|
||||||
await initAndLaunchTestApp(tester, () async {
|
// await initAndLaunchTestApp(tester, () async {
|
||||||
// Initialize dat for mocked classes
|
// // Initialize dat for mocked classes
|
||||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
// when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||||
.thenAnswer((i) => Stream.value(true));
|
// .thenAnswer((i) => Stream.value(true));
|
||||||
when((getIt<LocalVault>() as MockLocalVault)
|
// when((getIt<LocalVault>() as MockLocalVault)
|
||||||
.loadAuthenticationInformation())
|
// .loadAuthenticationInformation())
|
||||||
.thenAnswer((realInvocation) async => null);
|
// .thenAnswer((realInvocation) async => null);
|
||||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||||
preferredLocaleSubtag: 'en',
|
// preferredLocaleSubtag: 'en',
|
||||||
preferredThemeMode: ThemeMode.light,
|
// preferredThemeMode: ThemeMode.light,
|
||||||
isLocalAuthenticationEnabled: false,
|
// isLocalAuthenticationEnabled: false,
|
||||||
preferredViewType: ViewType.list,
|
// preferredViewType: ViewType.list,
|
||||||
showInboxOnStartup: false,
|
// showInboxOnStartup: false,
|
||||||
));
|
// ));
|
||||||
when(getIt<PaperlessAuthenticationApi>().login(
|
// when(getIt<PaperlessAuthenticationApi>().login(
|
||||||
username: testUsername,
|
// username: testUsername,
|
||||||
password: testPassword,
|
// password: testPassword,
|
||||||
)).thenAnswer((i) => Future.value("eyTestToken"));
|
// )).thenAnswer((i) => Future.value("eyTestToken"));
|
||||||
|
|
||||||
await getIt<ConnectivityCubit>().initialize();
|
// await getIt<ConnectivityCubit>().initialize();
|
||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Mocked classes
|
// // Mocked classes
|
||||||
|
|
||||||
await t.binding.waitUntilFirstFrameRasterized;
|
// await t.binding.waitUntilFirstFrameRasterized;
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(serverAddressField, testServerUrl);
|
// await tester.enterText(serverAddressField, testServerUrl);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(usernameField, testUsername);
|
// await tester.enterText(usernameField, testUsername);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(passwordField, testPassword);
|
// await tester.enterText(passwordField, testPassword);
|
||||||
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
// FocusManager.instance.primaryFocus?.unfocus();
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(loginBtn);
|
// await tester.tap(loginBtn);
|
||||||
|
|
||||||
verify(getIt<PaperlessAuthenticationApi>().login(
|
// verify(getIt<PaperlessAuthenticationApi>().login(
|
||||||
username: testUsername,
|
// username: testUsername,
|
||||||
password: testPassword,
|
// password: testPassword,
|
||||||
)).called(1);
|
// )).called(1);
|
||||||
});
|
// });
|
||||||
|
|
||||||
testWidgets('Test login validation missing password',
|
// testWidgets('Test login validation missing password',
|
||||||
(WidgetTester tester) async {
|
// (WidgetTester tester) async {
|
||||||
await initAndLaunchTestApp(tester, () async {
|
// await initAndLaunchTestApp(tester, () async {
|
||||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
// when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||||
.connectivityChanges())
|
// .connectivityChanges())
|
||||||
.thenAnswer((i) => Stream.value(true));
|
// .thenAnswer((i) => Stream.value(true));
|
||||||
when((getIt<LocalVault>() as MockLocalVault)
|
// when((getIt<LocalVault>() as MockLocalVault)
|
||||||
.loadAuthenticationInformation())
|
// .loadAuthenticationInformation())
|
||||||
.thenAnswer((realInvocation) async => null);
|
// .thenAnswer((realInvocation) async => null);
|
||||||
|
|
||||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||||
preferredLocaleSubtag: 'en',
|
// preferredLocaleSubtag: 'en',
|
||||||
preferredThemeMode: ThemeMode.light,
|
// preferredThemeMode: ThemeMode.light,
|
||||||
isLocalAuthenticationEnabled: false,
|
// isLocalAuthenticationEnabled: false,
|
||||||
preferredViewType: ViewType.list,
|
// preferredViewType: ViewType.list,
|
||||||
showInboxOnStartup: false,
|
// showInboxOnStartup: false,
|
||||||
));
|
// ));
|
||||||
|
|
||||||
await getIt<ConnectivityCubit>().initialize();
|
// await getIt<ConnectivityCubit>().initialize();
|
||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||||
});
|
// });
|
||||||
// Mocked classes
|
// // Mocked classes
|
||||||
|
|
||||||
// Initialize dat for mocked classes
|
// // Initialize dat for mocked classes
|
||||||
|
|
||||||
await t.binding.waitUntilFirstFrameRasterized;
|
// await t.binding.waitUntilFirstFrameRasterized;
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(serverAddressField, testServerUrl);
|
// await tester.enterText(serverAddressField, testServerUrl);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(usernameField, testUsername);
|
// await tester.enterText(usernameField, testUsername);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
// FocusManager.instance.primaryFocus?.unfocus();
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(loginBtn);
|
// await tester.tap(loginBtn);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
verifyNever(
|
// verifyNever(
|
||||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
// (getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||||
.login(
|
// .login(
|
||||||
username: testUsername,
|
// username: testUsername,
|
||||||
password: testPassword,
|
// password: testPassword,
|
||||||
));
|
// ));
|
||||||
expect(
|
// expect(
|
||||||
find.textContaining(t.translations.loginPagePasswordValidatorMessageText),
|
// find.textContaining(t.translations.loginPagePasswordValidatorMessageText),
|
||||||
findsOneWidget,
|
// findsOneWidget,
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
|
|
||||||
testWidgets('Test login validation missing username',
|
// testWidgets('Test login validation missing username',
|
||||||
(WidgetTester tester) async {
|
// (WidgetTester tester) async {
|
||||||
await initAndLaunchTestApp(tester, () async {
|
// await initAndLaunchTestApp(tester, () async {
|
||||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
// when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||||
.connectivityChanges())
|
// .connectivityChanges())
|
||||||
.thenAnswer((i) => Stream.value(true));
|
// .thenAnswer((i) => Stream.value(true));
|
||||||
when((getIt<LocalVault>() as MockLocalVault)
|
// when((getIt<LocalVault>() as MockLocalVault)
|
||||||
.loadAuthenticationInformation())
|
// .loadAuthenticationInformation())
|
||||||
.thenAnswer((realInvocation) async => null);
|
// .thenAnswer((realInvocation) async => null);
|
||||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||||
preferredLocaleSubtag: 'en',
|
// preferredLocaleSubtag: 'en',
|
||||||
preferredThemeMode: ThemeMode.light,
|
// preferredThemeMode: ThemeMode.light,
|
||||||
isLocalAuthenticationEnabled: false,
|
// isLocalAuthenticationEnabled: false,
|
||||||
preferredViewType: ViewType.list,
|
// preferredViewType: ViewType.list,
|
||||||
showInboxOnStartup: false,
|
// showInboxOnStartup: false,
|
||||||
));
|
// ));
|
||||||
await getIt<ConnectivityCubit>().initialize();
|
// await getIt<ConnectivityCubit>().initialize();
|
||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||||
});
|
// });
|
||||||
|
|
||||||
await t.binding.waitUntilFirstFrameRasterized;
|
// await t.binding.waitUntilFirstFrameRasterized;
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(serverAddressField, testServerUrl);
|
// await tester.enterText(serverAddressField, testServerUrl);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(passwordField, testPassword);
|
// await tester.enterText(passwordField, testPassword);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
// FocusManager.instance.primaryFocus?.unfocus();
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(loginBtn);
|
// await tester.tap(loginBtn);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
verifyNever(
|
// verifyNever(
|
||||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
// (getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||||
.login(
|
// .login(
|
||||||
username: testUsername,
|
// username: testUsername,
|
||||||
password: testPassword,
|
// password: testPassword,
|
||||||
));
|
// ));
|
||||||
expect(
|
// expect(
|
||||||
find.textContaining(t.translations.loginPageUsernameValidatorMessageText),
|
// find.textContaining(t.translations.loginPageUsernameValidatorMessageText),
|
||||||
findsOneWidget,
|
// findsOneWidget,
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
|
|
||||||
testWidgets('Test login validation missing server address',
|
// testWidgets('Test login validation missing server address',
|
||||||
(WidgetTester tester) async {
|
// (WidgetTester tester) async {
|
||||||
initAndLaunchTestApp(tester, () async {
|
// initAndLaunchTestApp(tester, () async {
|
||||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
// when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||||
.thenAnswer((i) => Stream.value(true));
|
// .thenAnswer((i) => Stream.value(true));
|
||||||
|
|
||||||
when((getIt<LocalVault>()).loadAuthenticationInformation())
|
// when((getIt<LocalVault>()).loadAuthenticationInformation())
|
||||||
.thenAnswer((realInvocation) async => null);
|
// .thenAnswer((realInvocation) async => null);
|
||||||
|
|
||||||
when((getIt<LocalVault>()).loadApplicationSettings())
|
// when((getIt<LocalVault>()).loadApplicationSettings())
|
||||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||||
preferredLocaleSubtag: 'en',
|
// preferredLocaleSubtag: 'en',
|
||||||
preferredThemeMode: ThemeMode.light,
|
// preferredThemeMode: ThemeMode.light,
|
||||||
isLocalAuthenticationEnabled: false,
|
// isLocalAuthenticationEnabled: false,
|
||||||
preferredViewType: ViewType.list,
|
// preferredViewType: ViewType.list,
|
||||||
showInboxOnStartup: false,
|
// showInboxOnStartup: false,
|
||||||
));
|
// ));
|
||||||
|
|
||||||
await getIt<ConnectivityCubit>().initialize();
|
// await getIt<ConnectivityCubit>().initialize();
|
||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||||
});
|
// });
|
||||||
|
|
||||||
await t.binding.waitUntilFirstFrameRasterized;
|
// await t.binding.waitUntilFirstFrameRasterized;
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(usernameField, testUsername);
|
// await tester.enterText(usernameField, testUsername);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(passwordField, testPassword);
|
// await tester.enterText(passwordField, testPassword);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
// FocusManager.instance.primaryFocus?.unfocus();
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(loginBtn);
|
// await tester.tap(loginBtn);
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
|
|
||||||
verifyNever(getIt<PaperlessAuthenticationApi>().login(
|
// verifyNever(getIt<PaperlessAuthenticationApi>().login(
|
||||||
username: testUsername,
|
// username: testUsername,
|
||||||
password: testPassword,
|
// password: testPassword,
|
||||||
));
|
// ));
|
||||||
expect(
|
// expect(
|
||||||
find.textContaining(
|
// find.textContaining(
|
||||||
t.translations.loginPageServerUrlValidatorMessageText),
|
// t.translations.loginPageServerUrlValidatorMessageText),
|
||||||
findsOneWidget,
|
// findsOneWidget,
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
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/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
Future<TestingFrameworkVariables> initializeTestingFramework(
|
Future<TestingFrameworkVariables> initializeTestingFramework(
|
||||||
{String languageCode = 'en'}) async {
|
{String languageCode = 'en'}) async {
|
||||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
configureDependencies('test');
|
|
||||||
final translations = await S.load(
|
final translations = await S.load(
|
||||||
Locale.fromSubtags(
|
Locale.fromSubtags(
|
||||||
languageCode: languageCode,
|
languageCode: languageCode,
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// Fix for accepting self signed certificates.
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
class X509HttpOverrides extends HttpOverrides {
|
|
||||||
@override
|
|
||||||
HttpClient createHttpClient(SecurityContext? context) {
|
|
||||||
return super.createHttpClient(context)
|
|
||||||
..badCertificateCallback =
|
|
||||||
(X509Certificate cert, String host, int port) => true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,8 @@ import 'dart:developer';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http_interceptor/http_interceptor.dart';
|
import 'package:http_interceptor/http_interceptor.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
|
|
||||||
@prod
|
|
||||||
@injectable
|
|
||||||
class AuthenticationInterceptor implements InterceptorContract {
|
class AuthenticationInterceptor implements InterceptorContract {
|
||||||
final LocalVault _localVault;
|
final LocalVault _localVault;
|
||||||
AuthenticationInterceptor(this._localVault);
|
AuthenticationInterceptor(this._localVault);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import 'package:http_interceptor/http_interceptor.dart';
|
import 'package:http_interceptor/http_interceptor.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
|
|
||||||
@prod
|
|
||||||
@injectable
|
|
||||||
class BaseUrlInterceptor implements InterceptorContract {
|
class BaseUrlInterceptor implements InterceptorContract {
|
||||||
final LocalVault _localVault;
|
final LocalVault _localVault;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||||
import 'package:http_interceptor/http_interceptor.dart';
|
import 'package:http_interceptor/http_interceptor.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
@injectable
|
|
||||||
class LanguageHeaderInterceptor implements InterceptorContract {
|
class LanguageHeaderInterceptor implements InterceptorContract {
|
||||||
final ApplicationSettingsCubit appSettingsCubit;
|
final ApplicationSettingsCubit appSettingsCubit;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import 'package:http_interceptor/http_interceptor.dart';
|
import 'package:http_interceptor/http_interceptor.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
const interceptedRoutes = ['thumb/'];
|
const interceptedRoutes = ['thumb/'];
|
||||||
|
|
||||||
@injectable
|
|
||||||
@prod
|
|
||||||
class ResponseConversionInterceptor implements InterceptorContract {
|
class ResponseConversionInterceptor implements InterceptorContract {
|
||||||
@override
|
@override
|
||||||
Future<BaseRequest> interceptRequest({required BaseRequest request}) async =>
|
Future<BaseRequest> interceptRequest({required BaseRequest request}) async =>
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Convenience class which handles timeout errors.
|
|
||||||
///
|
|
||||||
@prod
|
|
||||||
@Named("timeoutClient")
|
|
||||||
@Injectable(as: BaseClient)
|
|
||||||
class TimeoutClient implements BaseClient {
|
|
||||||
final ConnectivityStatusService connectivityStatusService;
|
|
||||||
static const Duration requestTimeout = Duration(seconds: 25);
|
|
||||||
|
|
||||||
TimeoutClient(this.connectivityStatusService);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) async {
|
|
||||||
return getIt<BaseClient>().send(request).timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void close() {
|
|
||||||
getIt<BaseClient>().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> delete(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
Object? body,
|
|
||||||
Encoding? encoding,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>()
|
|
||||||
.delete(url, headers: headers, body: body, encoding: encoding)
|
|
||||||
.timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> get(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>().get(url, headers: headers).timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> head(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>().head(url, headers: headers).timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> patch(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
Object? body,
|
|
||||||
Encoding? encoding,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>()
|
|
||||||
.patch(url, headers: headers, body: body, encoding: encoding)
|
|
||||||
.timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> post(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
Object? body,
|
|
||||||
Encoding? encoding,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>()
|
|
||||||
.post(url, headers: headers, body: body, encoding: encoding)
|
|
||||||
.timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response> put(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
Object? body,
|
|
||||||
Encoding? encoding,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return _handle400Error(
|
|
||||||
await getIt<BaseClient>()
|
|
||||||
.put(url, headers: headers, body: body, encoding: encoding)
|
|
||||||
.timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> read(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return getIt<BaseClient>().read(url, headers: headers).timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Uint8List> readBytes(
|
|
||||||
Uri url, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) async {
|
|
||||||
await _handleOfflineState();
|
|
||||||
return getIt<BaseClient>().readBytes(url, headers: headers).timeout(
|
|
||||||
requestTimeout,
|
|
||||||
onTimeout: () => Future.error(
|
|
||||||
const PaperlessServerException(ErrorCode.requestTimedOut)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Response _handle400Error(Response response) {
|
|
||||||
if (response.statusCode == 400) {
|
|
||||||
// try to parse contained error message, otherwise return response
|
|
||||||
final JSON json = jsonDecode(utf8.decode(response.bodyBytes));
|
|
||||||
final PaperlessValidationErrors errorMessages = {};
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
if (entry.value is List) {
|
|
||||||
errorMessages.putIfAbsent(
|
|
||||||
entry.key, () => (entry.value as List).cast<String>().first);
|
|
||||||
} else if (entry.value is String) {
|
|
||||||
errorMessages.putIfAbsent(entry.key, () => entry.value);
|
|
||||||
} else {
|
|
||||||
errorMessages.putIfAbsent(entry.key, () => entry.value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw errorMessages;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleOfflineState() async {
|
|
||||||
if (!(await connectivityStatusService.isConnectedToInternet())) {
|
|
||||||
throw const PaperlessServerException(ErrorCode.deviceOffline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,17 +11,21 @@ class LabelRepositoriesProvider extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiRepositoryProvider(
|
return MultiRepositoryProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider.value(
|
RepositoryProvider(
|
||||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
create: (context) =>
|
||||||
|
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||||
),
|
),
|
||||||
RepositoryProvider.value(
|
RepositoryProvider(
|
||||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
create: (context) =>
|
||||||
|
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||||
),
|
),
|
||||||
RepositoryProvider.value(
|
RepositoryProvider(
|
||||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
create: (context) =>
|
||||||
|
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||||
),
|
),
|
||||||
RepositoryProvider.value(
|
RepositoryProvider(
|
||||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
create: (context) =>
|
||||||
|
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: child,
|
child: child,
|
||||||
|
|||||||
100
lib/core/security/security_context_aware_dio_manager.dart
Normal file
100
lib/core/security/security_context_aware_dio_manager.dart
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/adapter.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Convenience http client handling timeouts.
|
||||||
|
///
|
||||||
|
class SecurityContextAwareDioManager {
|
||||||
|
final Dio _dio;
|
||||||
|
// Some dependencies require an [HttpClient], therefore this is also maintained here.
|
||||||
|
final HttpClient _httpClient;
|
||||||
|
SecurityContextAwareDioManager()
|
||||||
|
: _dio = _initDio(),
|
||||||
|
_httpClient = HttpClient();
|
||||||
|
|
||||||
|
Dio get client => _dio;
|
||||||
|
|
||||||
|
Stream<SecurityContext> get securityContextChanges =>
|
||||||
|
_securityContextStreamController.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
final StreamController<SecurityContext> _securityContextStreamController =
|
||||||
|
StreamController.broadcast();
|
||||||
|
|
||||||
|
static Dio _initDio() {
|
||||||
|
//en- and decoded by utf8 by default
|
||||||
|
final Dio dio = Dio(BaseOptions());
|
||||||
|
dio.options.receiveTimeout = const Duration(seconds: 25).inMilliseconds;
|
||||||
|
dio.options.responseType = ResponseType.json;
|
||||||
|
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||||
|
(client) => client
|
||||||
|
..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
dio.interceptors.add(
|
||||||
|
//TODO: Refactor, create own class...
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onError: (e, handler) {
|
||||||
|
//TODO: Implement and debug how error handling works, or if request has to be resolved.
|
||||||
|
if (e.response?.statusCode == 400) {
|
||||||
|
// try to parse contained error message, otherwise return response
|
||||||
|
final JSON json = jsonDecode(e.response?.data);
|
||||||
|
final PaperlessValidationErrors errorMessages = {};
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
if (entry.value is List) {
|
||||||
|
errorMessages.putIfAbsent(entry.key,
|
||||||
|
() => (entry.value as List).cast<String>().first);
|
||||||
|
} else if (entry.value is String) {
|
||||||
|
errorMessages.putIfAbsent(entry.key, () => entry.value);
|
||||||
|
} else {
|
||||||
|
errorMessages.putIfAbsent(
|
||||||
|
entry.key, () => entry.value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw errorMessages;
|
||||||
|
}
|
||||||
|
handler.next(e);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return dio;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSettings({
|
||||||
|
String? baseUrl,
|
||||||
|
String? authToken,
|
||||||
|
ClientCertificate? clientCertificate,
|
||||||
|
}) {
|
||||||
|
if (clientCertificate != null) {
|
||||||
|
final sc = SecurityContext()
|
||||||
|
..usePrivateKeyBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
)
|
||||||
|
..useCertificateChainBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
)
|
||||||
|
..setTrustedCertificatesBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
);
|
||||||
|
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||||
|
(client) => HttpClient(
|
||||||
|
context: sc,
|
||||||
|
)..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
_securityContextStreamController.add(sc);
|
||||||
|
}
|
||||||
|
if (baseUrl != null) {
|
||||||
|
_dio.options.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
if (authToken != null) {
|
||||||
|
_dio.options.headers.addAll({'Authorization': 'Token $authToken'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
lib/core/security/security_context_subject.dart
Normal file
26
lib/core/security/security_context_subject.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:http/io_client.dart';
|
||||||
|
import 'package:http_interceptor/http_interceptor.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
extension SecurityContextAwareBaseClientSubjectExtension
|
||||||
|
on BehaviorSubject<BaseClient> {
|
||||||
|
///
|
||||||
|
/// Registers new security context in a new [HttpClient].
|
||||||
|
///
|
||||||
|
|
||||||
|
BaseClient _createSecurityContextAwareHttpClient(
|
||||||
|
SecurityContext context, {
|
||||||
|
List<InterceptorContract> interceptors = const [],
|
||||||
|
}) {
|
||||||
|
Dio(BaseOptions());
|
||||||
|
return InterceptedClient.build(
|
||||||
|
client: IOClient(HttpClient(context: context)),
|
||||||
|
interceptors: interceptors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
lib/core/security/security_context_utils.dart
Normal file
0
lib/core/security/security_context_utils.dart
Normal file
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
abstract class ConnectivityStatusService {
|
abstract class ConnectivityStatusService {
|
||||||
Future<bool> isConnectedToInternet();
|
Future<bool> isConnectedToInternet();
|
||||||
@@ -9,8 +8,6 @@ abstract class ConnectivityStatusService {
|
|||||||
Stream<bool> connectivityChanges();
|
Stream<bool> connectivityChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@prod
|
|
||||||
@Injectable(as: ConnectivityStatusService)
|
|
||||||
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
||||||
final Connectivity connectivity;
|
final Connectivity connectivity;
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ class FileService {
|
|||||||
static Future<void> clearUserData() async {
|
static Future<void> clearUserData() async {
|
||||||
final scanDir = await scanDirectory;
|
final scanDir = await scanDirectory;
|
||||||
final tempDir = await temporaryDirectory;
|
final tempDir = await temporaryDirectory;
|
||||||
scanDir?.delete(recursive: true);
|
await scanDir?.delete(recursive: true);
|
||||||
tempDir.delete(recursive: true);
|
await tempDir.delete(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/model/document_processing_status.dart';
|
import 'package:paperless_mobile/core/model/document_processing_status.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
import 'package:web_socket_channel/io.dart';
|
import 'package:web_socket_channel/io.dart';
|
||||||
@@ -17,8 +15,6 @@ abstract class StatusService {
|
|||||||
AuthenticationInformation credentials, String documentFileName);
|
AuthenticationInformation credentials, String documentFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton(as: StatusService)
|
|
||||||
@Named("webSocketStatusService")
|
|
||||||
class WebSocketStatusService implements StatusService {
|
class WebSocketStatusService implements StatusService {
|
||||||
late WebSocket? socket;
|
late WebSocket? socket;
|
||||||
late IOWebSocketChannel? _channel;
|
late IOWebSocketChannel? _channel;
|
||||||
@@ -31,35 +27,33 @@ class WebSocketStatusService implements StatusService {
|
|||||||
AuthenticationInformation credentials,
|
AuthenticationInformation credentials,
|
||||||
String documentFileName,
|
String documentFileName,
|
||||||
) async {
|
) async {
|
||||||
socket = await WebSocket.connect(
|
// socket = await WebSocket.connect(
|
||||||
httpUrl.replaceFirst("http", "ws") + "/ws/status/",
|
// httpUrl.replaceFirst("http", "ws") + "/ws/status/",
|
||||||
customClient: getIt<HttpClient>(),
|
// customClient: getIt<HttpClient>(),
|
||||||
headers: {
|
// headers: {
|
||||||
'Authorization': 'Token ${credentials.token}',
|
// 'Authorization': 'Token ${credentials.token}',
|
||||||
},
|
// },
|
||||||
).catchError((_) {
|
// ).catchError((_) {
|
||||||
// Use long polling if connection could not be established
|
// // Use long polling if connection could not be established
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (socket != null) {
|
// if (socket != null) {
|
||||||
socket!.where(isNotNull).listen((event) {
|
// socket!.where(isNotNull).listen((event) {
|
||||||
final status = DocumentProcessingStatus.fromJson(event);
|
// final status = DocumentProcessingStatus.fromJson(event);
|
||||||
getIt<DocumentStatusCubit>().updateStatus(status);
|
// getIt<DocumentStatusCubit>().updateStatus(status);
|
||||||
if (status.currentProgress == 100) {
|
// if (status.currentProgress == 100) {
|
||||||
socket!.close();
|
// socket!.close();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable(as: StatusService)
|
|
||||||
@Named("longPollingStatusService")
|
|
||||||
class LongPollingStatusService implements StatusService {
|
class LongPollingStatusService implements StatusService {
|
||||||
static const maxRetries = 60;
|
static const maxRetries = 60;
|
||||||
|
|
||||||
final BaseClient httpClient;
|
final BaseClient httpClient;
|
||||||
LongPollingStatusService(@Named("timeoutClient") this.httpClient);
|
LongPollingStatusService(this.httpClient);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> startListeningBeforeDocumentUpload(
|
Future<void> startListeningBeforeDocumentUpload(
|
||||||
@@ -67,51 +61,51 @@ class LongPollingStatusService implements StatusService {
|
|||||||
AuthenticationInformation credentials,
|
AuthenticationInformation credentials,
|
||||||
String documentFileName,
|
String documentFileName,
|
||||||
) async {
|
) async {
|
||||||
final today = DateTime.now();
|
// final today = DateTime.now();
|
||||||
bool consumptionFinished = false;
|
// bool consumptionFinished = false;
|
||||||
int retryCount = 0;
|
// int retryCount = 0;
|
||||||
|
|
||||||
getIt<DocumentStatusCubit>().updateStatus(
|
// getIt<DocumentStatusCubit>().updateStatus(
|
||||||
DocumentProcessingStatus(
|
// DocumentProcessingStatus(
|
||||||
currentProgress: 0,
|
// currentProgress: 0,
|
||||||
filename: documentFileName,
|
// filename: documentFileName,
|
||||||
maxProgress: 100,
|
// maxProgress: 100,
|
||||||
message: ProcessingMessage.new_file,
|
// message: ProcessingMessage.new_file,
|
||||||
status: ProcessingStatus.working,
|
// status: ProcessingStatus.working,
|
||||||
taskId: DocumentProcessingStatus.unknownTaskId,
|
// taskId: DocumentProcessingStatus.unknownTaskId,
|
||||||
documentId: null,
|
// documentId: null,
|
||||||
isApproximated: true,
|
// isApproximated: true,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
do {
|
// do {
|
||||||
final response = await httpClient.get(
|
// final response = await httpClient.get(
|
||||||
Uri.parse(
|
// Uri.parse(
|
||||||
'$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'),
|
// '$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'),
|
||||||
);
|
// );
|
||||||
final data = await compute(
|
// final data = await compute(
|
||||||
PagedSearchResult.fromJson,
|
// PagedSearchResult.fromJson,
|
||||||
PagedSearchResultJsonSerializer(
|
// PagedSearchResultJsonSerializer(
|
||||||
jsonDecode(response.body), DocumentModel.fromJson),
|
// jsonDecode(response.body), DocumentModel.fromJson),
|
||||||
);
|
// );
|
||||||
if (data.count > 0) {
|
// if (data.count > 0) {
|
||||||
consumptionFinished = true;
|
// consumptionFinished = true;
|
||||||
final docId = data.results[0].id;
|
// final docId = data.results[0].id;
|
||||||
getIt<DocumentStatusCubit>().updateStatus(
|
// getIt<DocumentStatusCubit>().updateStatus(
|
||||||
DocumentProcessingStatus(
|
// DocumentProcessingStatus(
|
||||||
currentProgress: 100,
|
// currentProgress: 100,
|
||||||
filename: documentFileName,
|
// filename: documentFileName,
|
||||||
maxProgress: 100,
|
// maxProgress: 100,
|
||||||
message: ProcessingMessage.finished,
|
// message: ProcessingMessage.finished,
|
||||||
status: ProcessingStatus.success,
|
// status: ProcessingStatus.success,
|
||||||
taskId: DocumentProcessingStatus.unknownTaskId,
|
// taskId: DocumentProcessingStatus.unknownTaskId,
|
||||||
documentId: docId,
|
// documentId: docId,
|
||||||
isApproximated: true,
|
// isApproximated: true,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
sleep(const Duration(seconds: 1));
|
// sleep(const Duration(seconds: 1));
|
||||||
} while (!consumptionFinished && retryCount < maxRetries);
|
// } while (!consumptionFinished && retryCount < maxRetries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:paperless_mobile/core/type/types.dart';
|
|||||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
import 'package:paperless_mobile/features/login/model/authentication_information.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/settings/model/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
abstract class LocalVault {
|
abstract class LocalVault {
|
||||||
Future<void> storeAuthenticationInformation(AuthenticationInformation auth);
|
Future<void> storeAuthenticationInformation(AuthenticationInformation auth);
|
||||||
@@ -17,8 +16,6 @@ abstract class LocalVault {
|
|||||||
Future<void> clear();
|
Future<void> clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@prod
|
|
||||||
@Injectable(as: LocalVault)
|
|
||||||
class LocalVaultImpl implements LocalVault {
|
class LocalVaultImpl implements LocalVault {
|
||||||
static const applicationSettingsKey = "applicationSettings";
|
static const applicationSettingsKey = "applicationSettings";
|
||||||
static const authenticationKey = "authentication";
|
static const authenticationKey = "authentication";
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:paperless_mobile/di_initializer.config.dart';
|
|
||||||
import 'package:paperless_mobile/di_modules.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
final getIt = GetIt.instance..allowReassignment;
|
|
||||||
|
|
||||||
@InjectableInit(
|
|
||||||
initializerName: 'init', // default
|
|
||||||
preferRelativeImports: true, // default
|
|
||||||
asExtension: false, // default
|
|
||||||
includeMicroPackages: false,
|
|
||||||
)
|
|
||||||
void configureDependencies(String environment) =>
|
|
||||||
init(getIt, environment: environment);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Registers new security context, which will be used by the HttpClient, see [RegisterModule].
|
|
||||||
///
|
|
||||||
Future<void> registerSecurityContext(ClientCertificate? cert) async {
|
|
||||||
var context = SecurityContext();
|
|
||||||
if (cert != null) {
|
|
||||||
context = context
|
|
||||||
..usePrivateKeyBytes(cert.bytes, password: cert.passphrase)
|
|
||||||
..useCertificateChainBytes(cert.bytes, password: cert.passphrase)
|
|
||||||
..setTrustedCertificatesBytes(cert.bytes, password: cert.passphrase);
|
|
||||||
}
|
|
||||||
await getIt.unregister<SecurityContext>();
|
|
||||||
getIt.registerSingleton<SecurityContext>(context);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
||||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
||||||
import 'package:http_interceptor/http_interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/interceptor/authentication.interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/interceptor/base_url_interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/interceptor/response_conversion.interceptor.dart';
|
|
||||||
import 'package:http/io_client.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:local_auth/local_auth.dart';
|
|
||||||
|
|
||||||
@module
|
|
||||||
abstract class RegisterModule {
|
|
||||||
@prod
|
|
||||||
@singleton
|
|
||||||
LocalAuthentication get localAuthentication => LocalAuthentication();
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@singleton
|
|
||||||
EncryptedSharedPreferences get encryptedSharedPreferences =>
|
|
||||||
EncryptedSharedPreferences();
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@test
|
|
||||||
@singleton
|
|
||||||
@Order(-1)
|
|
||||||
SecurityContext get securityContext => SecurityContext();
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@singleton
|
|
||||||
Connectivity get connectivity => Connectivity();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Factory method creating an [HttpClient] with the currently registered [SecurityContext].
|
|
||||||
///
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
HttpClient getHttpClient(SecurityContext securityContext) =>
|
|
||||||
HttpClient(context: securityContext)
|
|
||||||
..connectionTimeout = const Duration(seconds: 10);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Factory method creating a [InterceptedClient] on top of the currently registered [HttpClient].
|
|
||||||
///
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
BaseClient getBaseClient(
|
|
||||||
AuthenticationInterceptor authInterceptor,
|
|
||||||
ResponseConversionInterceptor responseConversionInterceptor,
|
|
||||||
LanguageHeaderInterceptor languageHeaderInterceptor,
|
|
||||||
BaseUrlInterceptor baseUrlInterceptor,
|
|
||||||
HttpClient client,
|
|
||||||
) =>
|
|
||||||
InterceptedClient.build(
|
|
||||||
interceptors: [
|
|
||||||
baseUrlInterceptor,
|
|
||||||
authInterceptor,
|
|
||||||
responseConversionInterceptor,
|
|
||||||
languageHeaderInterceptor,
|
|
||||||
],
|
|
||||||
client: IOClient(client),
|
|
||||||
);
|
|
||||||
|
|
||||||
@prod
|
|
||||||
CacheManager getCacheManager(BaseClient client) => CacheManager(
|
|
||||||
Config('cacheKey', fileService: HttpFileService(httpClient: client)));
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
|
|
||||||
@module
|
|
||||||
abstract class PaperlessApiModule {
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
@injectable
|
|
||||||
PaperlessAuthenticationApi authenticationModule(BaseClient client) =>
|
|
||||||
PaperlessAuthenticationApiImpl(client);
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
@injectable
|
|
||||||
PaperlessLabelsApi labelsModule(
|
|
||||||
@Named('timeoutClient') BaseClient timeoutClient,
|
|
||||||
) =>
|
|
||||||
PaperlessLabelApiImpl(timeoutClient);
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
@injectable
|
|
||||||
PaperlessDocumentsApi documentsModule(
|
|
||||||
@Named('timeoutClient') BaseClient timeoutClient,
|
|
||||||
HttpClient httpClient,
|
|
||||||
) =>
|
|
||||||
PaperlessDocumentsApiImpl(timeoutClient, httpClient);
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
@injectable
|
|
||||||
PaperlessSavedViewsApi savedViewsModule(
|
|
||||||
@Named('timeoutClient') BaseClient timeoutClient,
|
|
||||||
) =>
|
|
||||||
PaperlessSavedViewsApiImpl(timeoutClient);
|
|
||||||
|
|
||||||
@prod
|
|
||||||
@Order(-1)
|
|
||||||
@injectable
|
|
||||||
PaperlessServerStatsApi serverStatsModule(
|
|
||||||
@Named('timeoutClient') BaseClient timeoutClient,
|
|
||||||
) =>
|
|
||||||
PaperlessServerStatsApiImpl(timeoutClient);
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:introduction_screen/introduction_screen.dart';
|
import 'package:introduction_screen/introduction_screen.dart';
|
||||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
||||||
@@ -26,8 +25,6 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async => false,
|
onWillPop: () async => false,
|
||||||
child: BlocProvider.value(
|
|
||||||
value: getIt<ApplicationSettingsCubit>(),
|
|
||||||
child: IntroductionScreen(
|
child: IntroductionScreen(
|
||||||
globalBackgroundColor: Theme.of(context).canvasColor,
|
globalBackgroundColor: Theme.of(context).canvasColor,
|
||||||
showDoneButton: true,
|
showDoneButton: true,
|
||||||
@@ -104,7 +101,6 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.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/util.dart';
|
|
||||||
|
|
||||||
class BiometricAuthenticationIntroSlide extends StatefulWidget {
|
|
||||||
const BiometricAuthenticationIntroSlide({
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<BiometricAuthenticationIntroSlide> createState() =>
|
|
||||||
_BiometricAuthenticationIntroSlideState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BiometricAuthenticationIntroSlideState
|
|
||||||
extends State<BiometricAuthenticationIntroSlide> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
//TODO: INTL
|
|
||||||
return BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
|
||||||
builder: (context, settings) {
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Configure Biometric Authentication",
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"It is highly recommended to additionally secure your local data. Do you want to enable biometric authentication?",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.fingerprint,
|
|
||||||
size: 48,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 32,
|
|
||||||
),
|
|
||||||
Builder(builder: (context) {
|
|
||||||
if (settings.isLocalAuthenticationEnabled) {
|
|
||||||
return ElevatedButton.icon(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.done,
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
label: Text("Enabled"),
|
|
||||||
onPressed: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return ElevatedButton(
|
|
||||||
child: Text("Enable"),
|
|
||||||
onPressed: () {
|
|
||||||
final settings =
|
|
||||||
BlocProvider.of<ApplicationSettingsCubit>(context)
|
|
||||||
.state;
|
|
||||||
getIt<LocalAuthenticationService>()
|
|
||||||
.authenticateLocalUser(
|
|
||||||
"Please authenticate to secure Paperless Mobile")
|
|
||||||
.then((isEnabled) {
|
|
||||||
if (!isEnabled) {
|
|
||||||
showSnackBar(context,
|
|
||||||
"Could not set up biometric authentication. Please try again or skip for now.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BlocProvider.of<ApplicationSettingsCubit>(context)
|
|
||||||
.setIsBiometricAuthenticationEnabled(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
||||||
@@ -26,6 +25,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.d
|
|||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
class DocumentDetailsPage extends StatefulWidget {
|
class DocumentDetailsPage extends StatefulWidget {
|
||||||
@@ -50,7 +50,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pop(BlocProvider.of<DocumentDetailsCubit>(context).state.document);
|
.pop(context.read<DocumentDetailsCubit>().state.document);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
@@ -106,9 +106,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
.black, //TODO: check if there is a way to dynamically determine color...
|
.black, //TODO: check if there is a way to dynamically determine color...
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(
|
onPressed: () => Navigator.of(context).pop(
|
||||||
BlocProvider.of<DocumentDetailsCubit>(context)
|
context.read<DocumentDetailsCubit>().state.document,
|
||||||
.state
|
|
||||||
.document,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floating: true,
|
floating: true,
|
||||||
@@ -185,29 +183,18 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
Future<void> _onEdit(DocumentModel document) async {
|
Future<void> _onEdit(DocumentModel document) async {
|
||||||
{
|
{
|
||||||
final cubit = BlocProvider.of<DocumentDetailsCubit>(context);
|
final cubit = context.read<DocumentDetailsCubit>();
|
||||||
Navigator.push<bool>(
|
Navigator.push<bool>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider(
|
builder: (context) => BlocProvider(
|
||||||
create: (context) => EditDocumentCubit(
|
create: (context) => EditDocumentCubit(
|
||||||
document,
|
document,
|
||||||
documentsApi: getIt<PaperlessDocumentsApi>(),
|
documentsApi: context.watch(),
|
||||||
correspondentRepository:
|
correspondentRepository: context.watch(),
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
documentTypeRepository: context.watch(),
|
||||||
context,
|
storagePathRepository: context.watch(),
|
||||||
),
|
tagRepository: context.watch(),
|
||||||
documentTypeRepository:
|
|
||||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
storagePathRepository:
|
|
||||||
RepositoryProvider.of<LabelRepository<StoragePath>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: BlocListener<EditDocumentCubit, EditDocumentState>(
|
child: BlocListener<EditDocumentCubit, EditDocumentState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
@@ -226,7 +213,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
||||||
return FutureBuilder<DocumentMetaData>(
|
return FutureBuilder<DocumentMetaData>(
|
||||||
future: getIt<PaperlessDocumentsApi>().getMetaData(document),
|
future: context.read<PaperlessDocumentsApi>().getMetaData(document),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -281,7 +268,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
Future<void> _assignAsn(DocumentModel document) async {
|
Future<void> _assignAsn(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentDetailsCubit>(context).assignAsn(document);
|
await context.read<DocumentDetailsCubit>().assignAsn(document);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
@@ -393,7 +380,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
///
|
///
|
||||||
Future<void> _onShare(DocumentModel document) async {
|
Future<void> _onShare(DocumentModel document) async {
|
||||||
Uint8List documentBytes =
|
Uint8List documentBytes =
|
||||||
await getIt<PaperlessDocumentsApi>().download(document);
|
await context.read<PaperlessDocumentsApi>().download(document);
|
||||||
final dir = await getTemporaryDirectory();
|
final dir = await getTemporaryDirectory();
|
||||||
final String path = "${dir.path}/${document.originalFileName}";
|
final String path = "${dir.path}/${document.originalFileName}";
|
||||||
await File(path).writeAsBytes(documentBytes);
|
await File(path).writeAsBytes(documentBytes);
|
||||||
@@ -419,7 +406,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
false;
|
false;
|
||||||
if (delete) {
|
if (delete) {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentDetailsCubit>(context).delete(document);
|
await context.read<DocumentDetailsCubit>().delete(document);
|
||||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
@@ -434,7 +421,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => DocumentView(
|
builder: (context) => DocumentView(
|
||||||
documentBytes: getIt<PaperlessDocumentsApi>().getPreview(document.id),
|
documentBytes:
|
||||||
|
context.read<PaperlessDocumentsApi>().getPreview(document.id),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentDownloadButton extends StatefulWidget {
|
class DocumentDownloadButton extends StatefulWidget {
|
||||||
final DocumentModel? document;
|
final DocumentModel? document;
|
||||||
@@ -43,7 +43,8 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
|||||||
}
|
}
|
||||||
setState(() => _isDownloadPending = true);
|
setState(() => _isDownloadPending = true);
|
||||||
try {
|
try {
|
||||||
final bytes = await getIt<PaperlessDocumentsApi>().download(document);
|
final bytes =
|
||||||
|
await context.read<PaperlessDocumentsApi>().download(document);
|
||||||
final Directory dir = await FileService.downloadsDirectory;
|
final Directory dir = await FileService.downloadsDirectory;
|
||||||
String filePath = "${dir.path}/${document.originalFileName}";
|
String filePath = "${dir.path}/${document.originalFileName}";
|
||||||
//TODO: Add replacement mechanism here (ask user if file should be replaced if exists)
|
//TODO: Add replacement mechanism here (ask user if file should be replaced if exists)
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
documentType: documentType,
|
documentType: documentType,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
authToken: auth.token!,
|
|
||||||
serverUrl: auth.serverUrl,
|
|
||||||
);
|
);
|
||||||
if (onConsumptionFinished != null) {
|
if (onConsumptionFinished != null) {
|
||||||
_documentApi
|
_documentApi
|
||||||
|
|||||||
@@ -166,10 +166,8 @@ class _DocumentUploadPreparationPageState
|
|||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialName) =>
|
labelCreationWidgetBuilder: (initialName) =>
|
||||||
RepositoryProvider.value(
|
RepositoryProvider<LabelRepository<DocumentType>>(
|
||||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
create: (context) => context.watch(),
|
||||||
context,
|
|
||||||
),
|
|
||||||
child: AddDocumentTypePage(initialName: initialName),
|
child: AddDocumentTypePage(initialName: initialName),
|
||||||
),
|
),
|
||||||
textFieldLabel:
|
textFieldLabel:
|
||||||
@@ -182,11 +180,8 @@ class _DocumentUploadPreparationPageState
|
|||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialName) =>
|
labelCreationWidgetBuilder: (initialName) =>
|
||||||
RepositoryProvider.value(
|
RepositoryProvider<LabelRepository<Correspondent>>(
|
||||||
value:
|
create: (context) => context.watch(),
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
child: AddCorrespondentPage(initialName: initialName),
|
child: AddCorrespondentPage(initialName: initialName),
|
||||||
),
|
),
|
||||||
textFieldLabel:
|
textFieldLabel:
|
||||||
@@ -220,7 +215,7 @@ class _DocumentUploadPreparationPageState
|
|||||||
|
|
||||||
void _onSubmit() async {
|
void _onSubmit() async {
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
final cubit = BlocProvider.of<DocumentUploadCubit>(context);
|
final cubit = context.read<DocumentUploadCubit>();
|
||||||
try {
|
try {
|
||||||
setState(() => _isUploadLoading = true);
|
setState(() => _isUploadLoading = true);
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
return LabelFormField<StoragePath>(
|
return LabelFormField<StoragePath>(
|
||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
labelCreationWidgetBuilder: (initialValue) =>
|
||||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
RepositoryProvider<LabelRepository<StoragePath>>(
|
||||||
|
create: (context) => context.watch(),
|
||||||
child: AddStoragePathPage(initalValue: initialValue),
|
child: AddStoragePathPage(initalValue: initialValue),
|
||||||
),
|
),
|
||||||
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
||||||
@@ -116,10 +117,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
return LabelFormField<Correspondent>(
|
return LabelFormField<Correspondent>(
|
||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
labelCreationWidgetBuilder: (initialValue) =>
|
||||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
RepositoryProvider<LabelRepository<Correspondent>>(
|
||||||
context,
|
create: context.watch(),
|
||||||
),
|
|
||||||
child: AddCorrespondentPage(initialName: initialValue),
|
child: AddCorrespondentPage(initialName: initialValue),
|
||||||
),
|
),
|
||||||
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
||||||
@@ -135,10 +135,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
return LabelFormField<DocumentType>(
|
return LabelFormField<DocumentType>(
|
||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value(
|
labelCreationWidgetBuilder: (currentInput) =>
|
||||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
RepositoryProvider<LabelRepository<DocumentType>>(
|
||||||
context,
|
create: (context) => context.watch(),
|
||||||
),
|
|
||||||
child: AddDocumentTypePage(
|
child: AddDocumentTypePage(
|
||||||
initialName: currentInput,
|
initialName: currentInput,
|
||||||
),
|
),
|
||||||
@@ -170,8 +169,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
_isSubmitLoading = true;
|
_isSubmitLoading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<EditDocumentCubit>(context)
|
await context.read<EditDocumentCubit>().updateDocument(mergedDocument);
|
||||||
.updateDocument(mergedDocument);
|
|
||||||
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
@@ -23,6 +22,7 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
|
|||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentsPage extends StatefulWidget {
|
class DocumentsPage extends StatefulWidget {
|
||||||
const DocumentsPage({Key? key}) : super(key: key);
|
const DocumentsPage({Key? key}) : super(key: key);
|
||||||
@@ -42,8 +42,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
_documentsCubit = context.watch();
|
||||||
_savedViewCubit = BlocProvider.of<SavedViewCubit>(context);
|
_savedViewCubit = context.watch();
|
||||||
try {
|
try {
|
||||||
_documentsCubit.load();
|
_documentsCubit.load();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
@@ -249,8 +249,11 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
||||||
DocumentModel document) {
|
DocumentModel document) {
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => BlocProvider.value(
|
builder: (_) => BlocProvider(
|
||||||
value: DocumentDetailsCubit(getIt<PaperlessDocumentsApi>(), document),
|
create: (context) => DocumentDetailsCubit(
|
||||||
|
context.watch(),
|
||||||
|
document,
|
||||||
|
),
|
||||||
child: const LabelRepositoriesProvider(
|
child: const LabelRepositoriesProvider(
|
||||||
child: DocumentDetailsPage(),
|
child: DocumentDetailsPage(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class DocumentPreview extends StatelessWidget {
|
class DocumentPreview extends StatelessWidget {
|
||||||
@@ -30,14 +30,15 @@ class DocumentPreview extends StatelessWidget {
|
|||||||
fit: fit,
|
fit: fit,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
cacheKey: "thumb_$id",
|
cacheKey: "thumb_$id",
|
||||||
imageUrl: getIt<PaperlessDocumentsApi>().getThumbnailUrl(id),
|
imageUrl:
|
||||||
|
Provider.of<PaperlessDocumentsApi>(context).getThumbnailUrl(id),
|
||||||
errorWidget: (ctxt, msg, __) => Text(msg),
|
errorWidget: (ctxt, msg, __) => Text(msg),
|
||||||
placeholder: (context, value) => Shimmer.fromColors(
|
placeholder: (context, value) => Shimmer.fromColors(
|
||||||
baseColor: Colors.grey[300]!,
|
baseColor: Colors.grey[300]!,
|
||||||
highlightColor: Colors.grey[100]!,
|
highlightColor: Colors.grey[100]!,
|
||||||
child: const SizedBox(height: 100, width: 100),
|
child: const SizedBox(height: 100, width: 100),
|
||||||
),
|
),
|
||||||
cacheManager: getIt<CacheManager>(),
|
cacheManager: context.watch(),
|
||||||
),
|
),
|
||||||
// ),
|
// ),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class SortDocumentsButton extends StatelessWidget {
|
|||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
builder: (_) => BlocProvider.value(
|
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||||
value: BlocProvider.of<DocumentsCubit>(context),
|
value: BlocProvider.of<DocumentsCubit>(context),
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
heightFactor: .6,
|
heightFactor: .6,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
||||||
@@ -15,6 +14,7 @@ import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart
|
|||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
@@ -56,7 +56,8 @@ class _HomePageState extends State<HomePage> {
|
|||||||
MultiBlocProvider(
|
MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
value:
|
||||||
|
DocumentsCubit(Provider.of<PaperlessDocumentsApi>(context)),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SavedViewCubit(
|
create: (context) => SavedViewCubit(
|
||||||
@@ -70,8 +71,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
value: _scannerCubit,
|
value: _scannerCubit,
|
||||||
child: const ScannerPage(),
|
child: const ScannerPage(),
|
||||||
),
|
),
|
||||||
BlocProvider.value(
|
BlocProvider(
|
||||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
create: (context) => DocumentsCubit(
|
||||||
|
Provider.of<PaperlessDocumentsApi>(context),
|
||||||
|
),
|
||||||
child: const LabelsPage(),
|
child: const LabelsPage(),
|
||||||
),
|
),
|
||||||
][_currentIndex],
|
][_currentIndex],
|
||||||
@@ -81,13 +84,12 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
void _initializeData(BuildContext context) {
|
void _initializeData(BuildContext context) {
|
||||||
try {
|
try {
|
||||||
RepositoryProvider.of<LabelRepository<Tag>>(context).findAll();
|
context.read<LabelRepository<Tag>>().findAll();
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context).findAll();
|
context.read<LabelRepository<Correspondent>>().findAll();
|
||||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context).findAll();
|
context.read<LabelRepository<DocumentType>>().findAll();
|
||||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context).findAll();
|
context.read<LabelRepository<StoragePath>>().findAll();
|
||||||
RepositoryProvider.of<SavedViewRepository>(context).findAll();
|
context.read<SavedViewRepository>().findAll();
|
||||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
context.read<PaperlessServerInformationCubit>().updateInformtion();
|
||||||
.updateInformtion();
|
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
||||||
@@ -7,7 +8,6 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
|||||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||||
@@ -16,14 +16,29 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
|
|||||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/link.dart';
|
import 'package:url_launcher/link.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
class InfoDrawer extends StatelessWidget {
|
class InfoDrawer extends StatefulWidget {
|
||||||
final VoidCallback? afterInboxClosed;
|
final VoidCallback? afterInboxClosed;
|
||||||
|
|
||||||
const InfoDrawer({Key? key, this.afterInboxClosed}) : super(key: key);
|
const InfoDrawer({Key? key, this.afterInboxClosed}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InfoDrawer> createState() => _InfoDrawerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfoDrawerState extends State<InfoDrawer> {
|
||||||
|
late final Future<PackageInfo> _packageInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_packageInfo = PackageInfo.fromPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
@@ -140,8 +155,9 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (context) => BlocProvider(
|
||||||
value: getIt<ApplicationSettingsCubit>(),
|
create: (context) =>
|
||||||
|
Provider.of<ApplicationSettingsCubit>(context),
|
||||||
child: const SettingsPage(),
|
child: const SettingsPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -155,13 +171,18 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
AboutListTile(
|
FutureBuilder<PackageInfo>(
|
||||||
|
future: _packageInfo,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return AboutListTile(
|
||||||
icon: const Icon(Icons.info),
|
icon: const Icon(Icons.info),
|
||||||
applicationIcon: const ImageIcon(
|
applicationIcon: const ImageIcon(
|
||||||
AssetImage('assets/logos/paperless_logo_green.png')),
|
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||||
|
),
|
||||||
applicationName: 'Paperless Mobile',
|
applicationName: 'Paperless Mobile',
|
||||||
applicationVersion:
|
applicationVersion: (snapshot.data?.version ?? '') +
|
||||||
kPackageInfo.version + '+' + kPackageInfo.buildNumber,
|
'+' +
|
||||||
|
(snapshot.data?.buildNumber ?? ''),
|
||||||
aboutBoxChildren: [
|
aboutBoxChildren: [
|
||||||
Text(
|
Text(
|
||||||
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
||||||
@@ -173,7 +194,8 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'https://github.com/astubenbord/paperless-mobile',
|
'https://github.com/astubenbord/paperless-mobile',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.tertiary),
|
color:
|
||||||
|
Theme.of(context).colorScheme.tertiary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -185,14 +207,15 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
_buildOnboardingImageCredits(),
|
_buildOnboardingImageCredits(),
|
||||||
],
|
],
|
||||||
child: Text(S.of(context).appDrawerAboutLabel),
|
child: Text(S.of(context).appDrawerAboutLabel),
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.logout),
|
leading: const Icon(Icons.logout),
|
||||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
try {
|
try {
|
||||||
BlocProvider.of<AuthenticationCubit>(context).logout();
|
BlocProvider.of<AuthenticationCubit>(context).logout();
|
||||||
getIt<LocalVault>().clear();
|
Provider.of<LocalVault>(context).clear();
|
||||||
BlocProvider.of<ApplicationSettingsCubit>(context).clear();
|
BlocProvider.of<ApplicationSettingsCubit>(context).clear();
|
||||||
RepositoryProvider.of<LabelRepository<Tag>>(context)
|
RepositoryProvider.of<LabelRepository<Tag>>(context)
|
||||||
.clear();
|
.clear();
|
||||||
@@ -224,7 +247,7 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => InboxCubit(
|
create: (context) => InboxCubit(
|
||||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||||
getIt<PaperlessDocumentsApi>(),
|
Provider.of<PaperlessDocumentsApi>(context),
|
||||||
)..loadInbox(),
|
)..loadInbox(),
|
||||||
child: const InboxPage(),
|
child: const InboxPage(),
|
||||||
),
|
),
|
||||||
@@ -232,7 +255,7 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
maintainState: false,
|
maintainState: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
afterInboxClosed?.call();
|
widget.afterInboxClosed?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
Link _buildOnboardingImageCredits() {
|
Link _buildOnboardingImageCredits() {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class InboxItem extends StatelessWidget {
|
class InboxItem extends StatelessWidget {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
@@ -48,9 +48,9 @@ class InboxItem extends StatelessWidget {
|
|||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => BlocProvider.value(
|
builder: (_) => BlocProvider(
|
||||||
value: DocumentDetailsCubit(
|
create: (context) => DocumentDetailsCubit(
|
||||||
getIt<PaperlessDocumentsApi>(),
|
Provider.of<PaperlessDocumentsApi>(context),
|
||||||
document,
|
document,
|
||||||
),
|
),
|
||||||
child: const LabelRepositoriesProvider(
|
child: const LabelRepositoriesProvider(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LabelItem<T extends Label> extends StatelessWidget {
|
class LabelItem<T extends Label> extends StatelessWidget {
|
||||||
final T label;
|
final T label;
|
||||||
@@ -45,9 +45,9 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (context) => BlocProvider(
|
||||||
value: LinkedDocumentsCubit(
|
create: (context) => LinkedDocumentsCubit(
|
||||||
getIt<PaperlessDocumentsApi>(),
|
Provider.of<PaperlessDocumentsApi>(context),
|
||||||
filter,
|
filter,
|
||||||
),
|
),
|
||||||
child: const LinkedDocumentsPage(),
|
child: const LinkedDocumentsPage(),
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LinkedDocumentsPage extends StatefulWidget {
|
class LinkedDocumentsPage extends StatefulWidget {
|
||||||
const LinkedDocumentsPage({super.key});
|
const LinkedDocumentsPage({super.key});
|
||||||
@@ -63,10 +63,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => BlocProvider(
|
||||||
BlocProvider<DocumentDetailsCubit>.value(
|
create: (context) => DocumentDetailsCubit(
|
||||||
value: DocumentDetailsCubit(
|
Provider.of<PaperlessDocumentsApi>(
|
||||||
getIt<PaperlessDocumentsApi>(),
|
context),
|
||||||
document,
|
document,
|
||||||
),
|
),
|
||||||
child: const DocumentDetailsPage(
|
child: const DocumentDetailsPage(
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_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/security/security_context_aware_dio_manager.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
import 'package:paperless_mobile/features/login/model/authentication_information.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/user_credentials.model.dart';
|
import 'package:paperless_mobile/features/login/model/user_credentials.model.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
|
|
||||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
class AuthenticationCubit extends HydratedCubit<AuthenticationState> {
|
||||||
final LocalAuthenticationService _localAuthService;
|
final LocalAuthenticationService _localAuthService;
|
||||||
PaperlessAuthenticationApi _authApi;
|
final PaperlessAuthenticationApi _authApi;
|
||||||
final LocalVault _localVault;
|
final LocalVault _localVault;
|
||||||
|
final SecurityContextAwareDioManager _dioWrapper;
|
||||||
|
|
||||||
AuthenticationCubit(
|
AuthenticationCubit(
|
||||||
this._localVault,
|
this._localVault,
|
||||||
this._localAuthService,
|
this._localAuthService,
|
||||||
this._authApi,
|
this._authApi,
|
||||||
|
this._dioWrapper,
|
||||||
) : super(AuthenticationState.initial);
|
) : super(AuthenticationState.initial);
|
||||||
|
|
||||||
Future<void> login({
|
Future<void> login({
|
||||||
@@ -28,22 +32,21 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
}) async {
|
}) async {
|
||||||
assert(credentials.username != null && credentials.password != null);
|
assert(credentials.username != null && credentials.password != null);
|
||||||
try {
|
try {
|
||||||
await registerSecurityContext(clientCertificate);
|
_dioWrapper.updateSettings(
|
||||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
baseUrl: serverUrl,
|
||||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
|
||||||
// Store information required to make requests
|
|
||||||
final currentAuth = AuthenticationInformation(
|
|
||||||
serverUrl: serverUrl,
|
|
||||||
clientCertificate: clientCertificate,
|
clientCertificate: clientCertificate,
|
||||||
);
|
);
|
||||||
await _localVault.storeAuthenticationInformation(currentAuth);
|
|
||||||
|
|
||||||
final token = await _authApi.login(
|
final token = await _authApi.login(
|
||||||
username: credentials.username!,
|
username: credentials.username!,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
);
|
);
|
||||||
|
|
||||||
final auth = currentAuth.copyWith(token: token);
|
final auth = AuthenticationInformation(
|
||||||
|
serverUrl: serverUrl,
|
||||||
|
clientCertificate: clientCertificate,
|
||||||
|
token: token,
|
||||||
|
);
|
||||||
|
|
||||||
await _localVault.storeAuthenticationInformation(auth);
|
await _localVault.storeAuthenticationInformation(auth);
|
||||||
|
|
||||||
@@ -83,9 +86,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
final localAuthSuccess = await _localAuthService
|
final localAuthSuccess = await _localAuthService
|
||||||
.authenticateLocalUser("Authenticate to log back in");
|
.authenticateLocalUser("Authenticate to log back in");
|
||||||
if (localAuthSuccess) {
|
if (localAuthSuccess) {
|
||||||
await registerSecurityContext(storedAuth.clientCertificate);
|
_dioWrapper.updateSettings(
|
||||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
clientCertificate: storedAuth.clientCertificate,
|
||||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
);
|
||||||
return emit(
|
return emit(
|
||||||
AuthenticationState(
|
AuthenticationState(
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
@@ -102,7 +105,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await registerSecurityContext(storedAuth.clientCertificate);
|
_dioWrapper.updateSettings(
|
||||||
|
clientCertificate: storedAuth.clientCertificate,
|
||||||
|
);
|
||||||
final authState = AuthenticationState(
|
final authState = AuthenticationState(
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
authentication: storedAuth,
|
authentication: storedAuth,
|
||||||
@@ -115,40 +120,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _localVault.clear();
|
await _localVault.clear();
|
||||||
|
await super.clear();
|
||||||
emit(AuthenticationState.initial);
|
emit(AuthenticationState.initial);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@override
|
||||||
class AuthenticationState {
|
AuthenticationState? fromJson(Map<String, dynamic> json) =>
|
||||||
final bool wasLoginStored;
|
AuthenticationState.fromJson(json);
|
||||||
final bool? wasLocalAuthenticationSuccessful;
|
|
||||||
final bool isAuthenticated;
|
@override
|
||||||
final AuthenticationInformation? authentication;
|
Map<String, dynamic>? toJson(AuthenticationState state) => state.toJson();
|
||||||
|
|
||||||
static final AuthenticationState initial = AuthenticationState(
|
|
||||||
wasLoginStored: false,
|
|
||||||
isAuthenticated: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
AuthenticationState({
|
|
||||||
required this.isAuthenticated,
|
|
||||||
required this.wasLoginStored,
|
|
||||||
this.wasLocalAuthenticationSuccessful,
|
|
||||||
this.authentication,
|
|
||||||
});
|
|
||||||
|
|
||||||
AuthenticationState copyWith({
|
|
||||||
bool? wasLoginStored,
|
|
||||||
bool? isAuthenticated,
|
|
||||||
AuthenticationInformation? authentication,
|
|
||||||
bool? wasLocalAuthenticationSuccessful,
|
|
||||||
}) {
|
|
||||||
return AuthenticationState(
|
|
||||||
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
|
||||||
wasLoginStored: wasLoginStored ?? this.wasLoginStored,
|
|
||||||
authentication: authentication ?? this.authentication,
|
|
||||||
wasLocalAuthenticationSuccessful: wasLocalAuthenticationSuccessful ??
|
|
||||||
this.wasLocalAuthenticationSuccessful,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
44
lib/features/login/bloc/authentication_state.dart
Normal file
44
lib/features/login/bloc/authentication_state.dart
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
||||||
|
|
||||||
|
part 'authentication_state.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class AuthenticationState {
|
||||||
|
final bool wasLoginStored;
|
||||||
|
final bool? wasLocalAuthenticationSuccessful;
|
||||||
|
final bool isAuthenticated;
|
||||||
|
final AuthenticationInformation? authentication;
|
||||||
|
|
||||||
|
static final AuthenticationState initial = AuthenticationState(
|
||||||
|
wasLoginStored: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
AuthenticationState({
|
||||||
|
required this.isAuthenticated,
|
||||||
|
required this.wasLoginStored,
|
||||||
|
this.wasLocalAuthenticationSuccessful,
|
||||||
|
this.authentication,
|
||||||
|
});
|
||||||
|
|
||||||
|
AuthenticationState copyWith({
|
||||||
|
bool? wasLoginStored,
|
||||||
|
bool? isAuthenticated,
|
||||||
|
AuthenticationInformation? authentication,
|
||||||
|
bool? wasLocalAuthenticationSuccessful,
|
||||||
|
}) {
|
||||||
|
return AuthenticationState(
|
||||||
|
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
||||||
|
wasLoginStored: wasLoginStored ?? this.wasLoginStored,
|
||||||
|
authentication: authentication ?? this.authentication,
|
||||||
|
wasLocalAuthenticationSuccessful: wasLocalAuthenticationSuccessful ??
|
||||||
|
this.wasLocalAuthenticationSuccessful,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory AuthenticationState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AuthenticationStateFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$AuthenticationStateToJson(this);
|
||||||
|
}
|
||||||
29
lib/features/login/bloc/authentication_state.g.dart
Normal file
29
lib/features/login/bloc/authentication_state.g.dart
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'authentication_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
AuthenticationState _$AuthenticationStateFromJson(Map<String, dynamic> json) =>
|
||||||
|
AuthenticationState(
|
||||||
|
isAuthenticated: json['isAuthenticated'] as bool,
|
||||||
|
wasLoginStored: json['wasLoginStored'] as bool,
|
||||||
|
wasLocalAuthenticationSuccessful:
|
||||||
|
json['wasLocalAuthenticationSuccessful'] as bool?,
|
||||||
|
authentication: json['authentication'] == null
|
||||||
|
? null
|
||||||
|
: AuthenticationInformation.fromJson(
|
||||||
|
json['authentication'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AuthenticationStateToJson(
|
||||||
|
AuthenticationState instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'wasLoginStored': instance.wasLoginStored,
|
||||||
|
'wasLocalAuthenticationSuccessful':
|
||||||
|
instance.wasLocalAuthenticationSuccessful,
|
||||||
|
'isAuthenticated': instance.isAuthenticated,
|
||||||
|
'authentication': instance.authentication,
|
||||||
|
};
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
|
||||||
class AuthenticationInformation {
|
part 'authentication_information.g.dart';
|
||||||
static const tokenKey = 'token';
|
|
||||||
static const serverUrlKey = 'serverUrl';
|
|
||||||
static const clientCertificateKey = 'clientCertificate';
|
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class AuthenticationInformation {
|
||||||
final String? token;
|
final String? token;
|
||||||
final String serverUrl;
|
final String serverUrl;
|
||||||
final ClientCertificate? clientCertificate;
|
final ClientCertificate? clientCertificate;
|
||||||
@@ -16,21 +15,6 @@ class AuthenticationInformation {
|
|||||||
this.clientCertificate,
|
this.clientCertificate,
|
||||||
});
|
});
|
||||||
|
|
||||||
AuthenticationInformation.fromJson(JSON json)
|
|
||||||
: token = json[tokenKey],
|
|
||||||
serverUrl = json[serverUrlKey],
|
|
||||||
clientCertificate = json[clientCertificateKey] != null
|
|
||||||
? ClientCertificate.fromJson(json[clientCertificateKey])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
JSON toJson() {
|
|
||||||
return {
|
|
||||||
tokenKey: token,
|
|
||||||
serverUrlKey: serverUrl,
|
|
||||||
clientCertificateKey: clientCertificate?.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isValid {
|
bool get isValid {
|
||||||
return serverUrl.isNotEmpty && (token?.isNotEmpty ?? false);
|
return serverUrl.isNotEmpty && (token?.isNotEmpty ?? false);
|
||||||
}
|
}
|
||||||
@@ -48,4 +32,9 @@ class AuthenticationInformation {
|
|||||||
(removeClientCertificate ? null : this.clientCertificate),
|
(removeClientCertificate ? null : this.clientCertificate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory AuthenticationInformation.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AuthenticationInformationFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$AuthenticationInformationToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
26
lib/features/login/model/authentication_information.g.dart
Normal file
26
lib/features/login/model/authentication_information.g.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'authentication_information.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
AuthenticationInformation _$AuthenticationInformationFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
AuthenticationInformation(
|
||||||
|
token: json['token'] as String?,
|
||||||
|
serverUrl: json['serverUrl'] as String,
|
||||||
|
clientCertificate: json['clientCertificate'] == null
|
||||||
|
? null
|
||||||
|
: ClientCertificate.fromJson(
|
||||||
|
json['clientCertificate'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AuthenticationInformationToJson(
|
||||||
|
AuthenticationInformation instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'token': instance.token,
|
||||||
|
'serverUrl': instance.serverUrl,
|
||||||
|
'clientCertificate': instance.clientCertificate,
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ServerAddressFormField extends StatefulWidget {
|
class ServerAddressFormField extends StatefulWidget {
|
||||||
static const String fkServerAddress = "serverAddress";
|
static const String fkServerAddress = "serverAddress";
|
||||||
@@ -64,7 +64,8 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
//https://stackoverflow.com/questions/49648022/check-whether-there-is-an-internet-connection-available-on-flutter-app
|
//https://stackoverflow.com/questions/49648022/check-whether-there-is-an-internet-connection-available-on-flutter-app
|
||||||
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
||||||
final isReachable =
|
final isReachable =
|
||||||
await getIt<ConnectivityStatusService>().isServerReachable(address);
|
await Provider.of<ConnectivityStatusService>(context, listen: false)
|
||||||
|
.isServerReachable(address);
|
||||||
if (isReachable) {
|
if (isReachable) {
|
||||||
setState(() => _reachabilityStatus = ReachabilityStatus.reachable);
|
setState(() => _reachabilityStatus = ReachabilityStatus.reachable);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
|
|
||||||
class DocumentScannerCubit extends Cubit<List<File>> {
|
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import 'package:paperless_mobile/core/repository/provider/label_repositories_pro
|
|||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||||
@@ -28,6 +27,7 @@ import 'package:paperless_mobile/util.dart';
|
|||||||
import 'package:pdf/pdf.dart';
|
import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ScannerPage extends StatefulWidget {
|
class ScannerPage extends StatefulWidget {
|
||||||
const ScannerPage({Key? key}) : super(key: key);
|
const ScannerPage({Key? key}) : super(key: key);
|
||||||
@@ -139,8 +139,8 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
builder: (_) => LabelRepositoriesProvider(
|
builder: (_) => LabelRepositoriesProvider(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
localVault: getIt<LocalVault>(),
|
localVault: Provider.of<LocalVault>(context),
|
||||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
documentApi: Provider.of<PaperlessDocumentsApi>(context),
|
||||||
correspondentRepository:
|
correspondentRepository:
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||||
context,
|
context,
|
||||||
@@ -274,8 +274,8 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
builder: (_) => LabelRepositoriesProvider(
|
builder: (_) => LabelRepositoriesProvider(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
localVault: getIt<LocalVault>(),
|
localVault: Provider.of<LocalVault>(context),
|
||||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
documentApi: Provider.of<PaperlessDocumentsApi>(context),
|
||||||
correspondentRepository:
|
correspondentRepository:
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1,24 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
|
||||||
@prod
|
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
||||||
@test
|
ApplicationSettingsCubit() : super(ApplicationSettingsState.defaultSettings);
|
||||||
@lazySingleton
|
|
||||||
class ApplicationSettingsCubit extends Cubit<ApplicationSettingsState> {
|
|
||||||
final LocalVault localVault;
|
|
||||||
|
|
||||||
ApplicationSettingsCubit(this.localVault)
|
|
||||||
: super(ApplicationSettingsState.defaultSettings);
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
|
||||||
final settings = (await localVault.loadApplicationSettings()) ??
|
|
||||||
ApplicationSettingsState.defaultSettings;
|
|
||||||
emit(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setLocale(String? localeSubtag) async {
|
Future<void> setLocale(String? localeSubtag) async {
|
||||||
final updatedSettings = state.copyWith(preferredLocaleSubtag: localeSubtag);
|
final updatedSettings = state.copyWith(preferredLocaleSubtag: localeSubtag);
|
||||||
@@ -42,11 +28,20 @@ class ApplicationSettingsCubit extends Cubit<ApplicationSettingsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateSettings(ApplicationSettingsState settings) async {
|
Future<void> _updateSettings(ApplicationSettingsState settings) async {
|
||||||
await localVault.storeApplicationSettings(settings);
|
|
||||||
emit(settings);
|
emit(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
@override
|
||||||
|
Future<void> clear() async {
|
||||||
|
await super.clear();
|
||||||
emit(ApplicationSettingsState.defaultSettings);
|
emit(ApplicationSettingsState.defaultSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ApplicationSettingsState? fromJson(Map<String, dynamic> json) =>
|
||||||
|
ApplicationSettingsState.fromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic>? toJson(ApplicationSettingsState state) =>
|
||||||
|
state.toJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,45 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
|
||||||
|
part 'application_settings_state.g.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
/// State holding the current application settings such as selected language, theme mode and more.
|
/// State holding the current application settings such as selected language, theme mode and more.
|
||||||
///
|
///
|
||||||
///
|
@JsonSerializable()
|
||||||
class ApplicationSettingsState {
|
class ApplicationSettingsState {
|
||||||
static final defaultSettings = ApplicationSettingsState(
|
static final defaultSettings = ApplicationSettingsState(
|
||||||
isLocalAuthenticationEnabled: false,
|
isLocalAuthenticationEnabled: false,
|
||||||
preferredLocaleSubtag: Platform.localeName.split('_').first,
|
preferredLocaleSubtag: Platform.localeName.split('_').first,
|
||||||
preferredThemeMode: ThemeMode.system,
|
preferredThemeMode: ThemeMode.system,
|
||||||
preferredViewType: ViewType.list,
|
preferredViewType: ViewType.list,
|
||||||
showInboxOnStartup: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
static const isLocalAuthenticationEnabledKey = "isLocalAuthenticationEnabled";
|
|
||||||
static const preferredLocaleSubtagKey = "localeSubtag";
|
|
||||||
static const preferredThemeModeKey = "preferredThemeModeKey";
|
|
||||||
static const preferredViewTypeKey = 'preferredViewType';
|
|
||||||
static const showInboxOnStartupKey = 'showinboxOnStartup';
|
|
||||||
|
|
||||||
final bool isLocalAuthenticationEnabled;
|
final bool isLocalAuthenticationEnabled;
|
||||||
final String preferredLocaleSubtag;
|
final String preferredLocaleSubtag;
|
||||||
final ThemeMode preferredThemeMode;
|
final ThemeMode preferredThemeMode;
|
||||||
final ViewType preferredViewType;
|
final ViewType preferredViewType;
|
||||||
final bool showInboxOnStartup;
|
|
||||||
|
|
||||||
ApplicationSettingsState({
|
ApplicationSettingsState({
|
||||||
required this.preferredLocaleSubtag,
|
required this.preferredLocaleSubtag,
|
||||||
required this.preferredThemeMode,
|
required this.preferredThemeMode,
|
||||||
required this.isLocalAuthenticationEnabled,
|
required this.isLocalAuthenticationEnabled,
|
||||||
required this.preferredViewType,
|
required this.preferredViewType,
|
||||||
required this.showInboxOnStartup,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
JSON toJson() {
|
Map<String, dynamic> toJson() => _$ApplicationSettingsStateToJson(this);
|
||||||
return {
|
factory ApplicationSettingsState.fromJson(Map<String, dynamic> json) =>
|
||||||
isLocalAuthenticationEnabledKey: isLocalAuthenticationEnabled,
|
_$ApplicationSettingsStateFromJson(json);
|
||||||
preferredLocaleSubtagKey: preferredLocaleSubtag,
|
|
||||||
preferredThemeModeKey: preferredThemeMode.name,
|
|
||||||
preferredViewTypeKey: preferredViewType.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationSettingsState.fromJson(JSON json)
|
|
||||||
: isLocalAuthenticationEnabled = json[isLocalAuthenticationEnabledKey] ??
|
|
||||||
defaultSettings.isLocalAuthenticationEnabled,
|
|
||||||
preferredLocaleSubtag = json[preferredLocaleSubtagKey] ??
|
|
||||||
defaultSettings.preferredLocaleSubtag,
|
|
||||||
preferredThemeMode = json.containsKey(preferredThemeModeKey)
|
|
||||||
? ThemeMode.values.byName(json[preferredThemeModeKey])
|
|
||||||
: defaultSettings.preferredThemeMode,
|
|
||||||
preferredViewType = json.containsKey(preferredViewTypeKey)
|
|
||||||
? ViewType.values.byName(json[preferredViewTypeKey])
|
|
||||||
: defaultSettings.preferredViewType,
|
|
||||||
showInboxOnStartup =
|
|
||||||
json[showInboxOnStartupKey] ?? defaultSettings.showInboxOnStartup;
|
|
||||||
|
|
||||||
ApplicationSettingsState copyWith({
|
ApplicationSettingsState copyWith({
|
||||||
bool? isLocalAuthenticationEnabled,
|
bool? isLocalAuthenticationEnabled,
|
||||||
String? preferredLocaleSubtag,
|
String? preferredLocaleSubtag,
|
||||||
ThemeMode? preferredThemeMode,
|
ThemeMode? preferredThemeMode,
|
||||||
ViewType? preferredViewType,
|
ViewType? preferredViewType,
|
||||||
bool? showInboxOnStartup,
|
|
||||||
}) {
|
}) {
|
||||||
return ApplicationSettingsState(
|
return ApplicationSettingsState(
|
||||||
isLocalAuthenticationEnabled:
|
isLocalAuthenticationEnabled:
|
||||||
@@ -74,7 +48,6 @@ class ApplicationSettingsState {
|
|||||||
preferredLocaleSubtag ?? this.preferredLocaleSubtag,
|
preferredLocaleSubtag ?? this.preferredLocaleSubtag,
|
||||||
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
|
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
|
||||||
preferredViewType: preferredViewType ?? this.preferredViewType,
|
preferredViewType: preferredViewType ?? this.preferredViewType,
|
||||||
showInboxOnStartup: showInboxOnStartup ?? this.showInboxOnStartup,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'application_settings_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
ApplicationSettingsState _$ApplicationSettingsStateFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
ApplicationSettingsState(
|
||||||
|
preferredLocaleSubtag: json['preferredLocaleSubtag'] as String,
|
||||||
|
preferredThemeMode:
|
||||||
|
$enumDecode(_$ThemeModeEnumMap, json['preferredThemeMode']),
|
||||||
|
isLocalAuthenticationEnabled:
|
||||||
|
json['isLocalAuthenticationEnabled'] as bool,
|
||||||
|
preferredViewType:
|
||||||
|
$enumDecode(_$ViewTypeEnumMap, json['preferredViewType']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ApplicationSettingsStateToJson(
|
||||||
|
ApplicationSettingsState instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'isLocalAuthenticationEnabled': instance.isLocalAuthenticationEnabled,
|
||||||
|
'preferredLocaleSubtag': instance.preferredLocaleSubtag,
|
||||||
|
'preferredThemeMode': _$ThemeModeEnumMap[instance.preferredThemeMode]!,
|
||||||
|
'preferredViewType': _$ViewTypeEnumMap[instance.preferredViewType]!,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ThemeModeEnumMap = {
|
||||||
|
ThemeMode.system: 'system',
|
||||||
|
ThemeMode.light: 'light',
|
||||||
|
ThemeMode.dark: 'dark',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ViewTypeEnumMap = {
|
||||||
|
ViewType.grid: 'grid',
|
||||||
|
ViewType.list: 'list',
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.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/application_settings_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class BiometricAuthenticationSetting extends StatelessWidget {
|
class BiometricAuthenticationSetting extends StatelessWidget {
|
||||||
const BiometricAuthenticationSetting({super.key});
|
const BiometricAuthenticationSetting({super.key});
|
||||||
@@ -28,7 +28,8 @@ class BiometricAuthenticationSetting extends StatelessWidget {
|
|||||||
: S
|
: S
|
||||||
.of(context)
|
.of(context)
|
||||||
.appSettingsDisableBiometricAuthenticationReasonText;
|
.appSettingsDisableBiometricAuthenticationReasonText;
|
||||||
final changeValue = await getIt<LocalAuthenticationService>()
|
final changeValue =
|
||||||
|
await Provider.of<LocalAuthenticationService>(context)
|
||||||
.authenticateLocalUser(localizedReason);
|
.authenticateLocalUser(localizedReason);
|
||||||
if (changeValue) {
|
if (changeValue) {
|
||||||
settingsBloc.setIsBiometricAuthenticationEnabled(val);
|
settingsBloc.setIsBiometricAuthenticationEnabled(val);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ClearStorageSetting extends StatelessWidget {
|
class ClearStorageSetting extends StatelessWidget {
|
||||||
const ClearStorageSetting({super.key});
|
const ClearStorageSetting({super.key});
|
||||||
@@ -12,12 +12,10 @@ class ClearStorageSetting extends StatelessWidget {
|
|||||||
title: Text("Clear data"),
|
title: Text("Clear data"),
|
||||||
subtitle:
|
subtitle:
|
||||||
Text("Remove downloaded files, scans and clear the cache's content"),
|
Text("Remove downloaded files, scans and clear the cache's content"),
|
||||||
onTap: _clearCache,
|
onTap: () {
|
||||||
|
Provider.of<cm.CacheManager>(context).emptyCache();
|
||||||
|
FileService.clearUserData();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearCache() async {
|
|
||||||
getIt<cm.CacheManager>().emptyCache();
|
|
||||||
FileService.clearUserData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
181
lib/main.dart
181
lib/main.dart
@@ -1,25 +1,28 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:http/io_client.dart';
|
||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:intl/intl_standalone.dart';
|
import 'package:intl/intl_standalone.dart';
|
||||||
|
import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/bloc_changes_observer.dart';
|
import 'package:paperless_mobile/core/bloc/bloc_changes_observer.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/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/global/http_self_signed_certificate_override.dart';
|
|
||||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||||
|
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
||||||
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
|
||||||
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
|
||||||
import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart';
|
||||||
@@ -27,78 +30,146 @@ import 'package:paperless_mobile/core/repository/impl/storage_path_repository_im
|
|||||||
import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
|
import 'package:paperless_mobile/core/security/security_context_aware_dio_manager.dart';
|
||||||
|
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.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/application_settings_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
Bloc.observer = BlocChangesObserver();
|
Bloc.observer = BlocChangesObserver();
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
HydratedBloc.storage = await HydratedStorage.build(
|
||||||
|
storageDirectory: await getApplicationDocumentsDirectory(),
|
||||||
|
);
|
||||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||||
Intl.systemLocale = await findSystemLocale();
|
await findSystemLocale();
|
||||||
|
|
||||||
// Required for self signed client certificates
|
// Required for self signed client certificates
|
||||||
HttpOverrides.global = X509HttpOverrides();
|
final dioWrapper = SecurityContextAwareDioManager();
|
||||||
|
IOClient httpClient = IOClient();
|
||||||
|
|
||||||
configureDependencies('prod');
|
dioWrapper.securityContextChanges.listen(
|
||||||
|
(context) => httpClient = IOClient(HttpClient(context: context)),
|
||||||
|
);
|
||||||
|
// Initialize External dependencies
|
||||||
|
final connectivity = Connectivity();
|
||||||
|
final encryptedSharedPreferences = EncryptedSharedPreferences();
|
||||||
|
final localAuthentication = LocalAuthentication();
|
||||||
|
final cacheManager = cm.CacheManager(cm.Config('cacheKey',
|
||||||
|
fileService: cm.HttpFileService(httpClient: httpClient)));
|
||||||
|
|
||||||
|
// Initialize Paperless APIs
|
||||||
|
final authApi = PaperlessAuthenticationApiImpl(dioWrapper.client);
|
||||||
|
final documentsApi = PaperlessDocumentsApiImpl(dioWrapper.client);
|
||||||
|
final labelsApi = PaperlessLabelApiImpl(dioWrapper.client);
|
||||||
|
final statsApi = PaperlessServerStatsApiImpl(dioWrapper.client);
|
||||||
|
final savedViewsApi = PaperlessSavedViewsApiImpl(dioWrapper.client);
|
||||||
|
|
||||||
|
// Initialize other utility classes
|
||||||
|
final connectivityStatusService = ConnectivityStatusServiceImpl(connectivity);
|
||||||
|
final localVault = LocalVaultImpl(encryptedSharedPreferences);
|
||||||
|
final localAuthService =
|
||||||
|
LocalAuthenticationService(localVault, localAuthentication);
|
||||||
|
|
||||||
|
// Initialize Repositories
|
||||||
|
|
||||||
|
// Initialize Blocs/Cubits
|
||||||
|
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
||||||
// Remove temporarily downloaded files.
|
// Remove temporarily downloaded files.
|
||||||
|
|
||||||
(await FileService.temporaryDirectory).deleteSync(recursive: true);
|
(await FileService.temporaryDirectory).deleteSync(recursive: true);
|
||||||
kPackageInfo = await PackageInfo.fromPlatform();
|
|
||||||
// Load application settings and stored authentication data
|
// Load application settings and stored authentication data
|
||||||
await getIt<ConnectivityCubit>().initialize();
|
await connectivityCubit.initialize();
|
||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
|
||||||
|
|
||||||
final authCubit = AuthenticationCubit(
|
final authCubit = AuthenticationCubit(
|
||||||
getIt<LocalVault>(),
|
localVault,
|
||||||
getIt<LocalAuthenticationService>(),
|
localAuthService,
|
||||||
getIt<PaperlessAuthenticationApi>(),
|
authApi,
|
||||||
|
dioWrapper,
|
||||||
);
|
);
|
||||||
await authCubit.restoreSessionState();
|
//TODO: Check if hydrated cubit restores state.
|
||||||
|
//await authCubit.restoreSessionState();
|
||||||
|
|
||||||
// Create repositories
|
// Create repositories
|
||||||
final LabelRepository<Tag> tagRepository =
|
final tagRepository = TagRepositoryImpl(labelsApi);
|
||||||
TagRepositoryImpl(getIt<PaperlessLabelsApi>());
|
final correspondentRepository = CorrespondentRepositoryImpl(labelsApi);
|
||||||
final LabelRepository<Correspondent> correspondentRepository =
|
final documentTypeRepository = DocumentTypeRepositoryImpl(labelsApi);
|
||||||
CorrespondentRepositoryImpl(getIt<PaperlessLabelsApi>());
|
final storagePathRepository = StoragePathRepositoryImpl(labelsApi);
|
||||||
final LabelRepository<DocumentType> documentTypeRepository =
|
final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi);
|
||||||
DocumentTypeRepositoryImpl(getIt<PaperlessLabelsApi>());
|
|
||||||
final LabelRepository<StoragePath> storagePathRepository =
|
|
||||||
StoragePathRepositoryImpl(getIt<PaperlessLabelsApi>());
|
|
||||||
final SavedViewRepository savedViewRepository =
|
|
||||||
SavedViewRepositoryImpl(getIt<PaperlessSavedViewsApi>());
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiRepositoryProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider.value(value: tagRepository),
|
Provider<PaperlessAuthenticationApi>.value(value: authApi),
|
||||||
RepositoryProvider.value(value: correspondentRepository),
|
Provider<PaperlessDocumentsApi>.value(value: documentsApi),
|
||||||
RepositoryProvider.value(value: documentTypeRepository),
|
Provider<PaperlessLabelsApi>.value(value: labelsApi),
|
||||||
RepositoryProvider.value(value: storagePathRepository),
|
Provider<PaperlessServerStatsApi>.value(value: statsApi),
|
||||||
RepositoryProvider.value(value: savedViewRepository),
|
Provider<PaperlessSavedViewsApi>.value(value: savedViewsApi),
|
||||||
|
Provider<cm.CacheManager>.value(value: cacheManager),
|
||||||
|
Provider<LocalVault>.value(value: localVault),
|
||||||
|
Provider<ConnectivityStatusService>.value(
|
||||||
|
value: connectivityStatusService,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: PaperlessMobileEntrypoint(authenticationCubit: authCubit),
|
child: MultiRepositoryProvider(
|
||||||
|
providers: [
|
||||||
|
RepositoryProvider<LabelRepository<Tag>>.value(
|
||||||
|
value: tagRepository,
|
||||||
|
),
|
||||||
|
RepositoryProvider<LabelRepository<Correspondent>>.value(
|
||||||
|
value: correspondentRepository,
|
||||||
|
),
|
||||||
|
RepositoryProvider<LabelRepository<DocumentType>>.value(
|
||||||
|
value: documentTypeRepository,
|
||||||
|
),
|
||||||
|
RepositoryProvider<LabelRepository<StoragePath>>.value(
|
||||||
|
value: storagePathRepository,
|
||||||
|
),
|
||||||
|
RepositoryProvider<SavedViewRepository>.value(
|
||||||
|
value: savedViewRepository,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AuthenticationCubit(
|
||||||
|
localVault,
|
||||||
|
localAuthService,
|
||||||
|
authApi,
|
||||||
|
dioWrapper,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider<ConnectivityCubit>.value(
|
||||||
|
value: connectivityCubit,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const PaperlessMobileEntrypoint(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||||
final AuthenticationCubit authenticationCubit;
|
|
||||||
const PaperlessMobileEntrypoint({
|
const PaperlessMobileEntrypoint({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.authenticationCubit,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -153,14 +224,14 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ConnectivityCubit>.value(
|
BlocProvider(
|
||||||
value: getIt<ConnectivityCubit>(),
|
create: (context) => ConnectivityCubit(context.watch()),
|
||||||
),
|
),
|
||||||
BlocProvider<PaperlessServerInformationCubit>.value(
|
BlocProvider(
|
||||||
value: getIt<PaperlessServerInformationCubit>(),
|
create: (context) => PaperlessServerInformationCubit(context.watch()),
|
||||||
),
|
),
|
||||||
BlocProvider<ApplicationSettingsCubit>.value(
|
BlocProvider(
|
||||||
value: getIt<ApplicationSettingsCubit>(),
|
create: (context) => ApplicationSettingsCubit(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||||
@@ -188,10 +259,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
GlobalWidgetsLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
FormBuilderLocalizations.delegate,
|
FormBuilderLocalizations.delegate,
|
||||||
],
|
],
|
||||||
home: BlocProvider.value(
|
home: const AuthenticationWrapper(),
|
||||||
value: widget.authenticationCubit,
|
|
||||||
child: const AuthenticationWrapper(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -247,19 +315,11 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider(
|
builder: (context) => BlocProvider(
|
||||||
create: (BuildContext context) => DocumentUploadCubit(
|
create: (BuildContext context) => DocumentUploadCubit(
|
||||||
localVault: getIt<LocalVault>(),
|
localVault: context.watch(),
|
||||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
documentApi: context.watch(),
|
||||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
tagRepository: context.watch(),
|
||||||
context,
|
correspondentRepository: context.watch(),
|
||||||
),
|
documentTypeRepository: context.watch(),
|
||||||
correspondentRepository:
|
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
documentTypeRepository:
|
|
||||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: DocumentUploadPreparationPage(
|
child: DocumentUploadPreparationPage(
|
||||||
fileBytes: bytes,
|
fileBytes: bytes,
|
||||||
@@ -349,13 +409,12 @@ class BiometricAuthenticationPage extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () =>
|
onPressed: () => context.read<AuthenticationCubit>().logout(),
|
||||||
BlocProvider.of<AuthenticationCubit>(context).logout(),
|
|
||||||
child: const Text("Log out"),
|
child: const Text("Log out"),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => BlocProvider.of<AuthenticationCubit>(context)
|
onPressed: () =>
|
||||||
.restoreSessionState(),
|
context.read<AuthenticationCubit>().restoreSessionState(),
|
||||||
child: const Text("Authenticate"),
|
child: const Text("Authenticate"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
|||||||
|
|
||||||
final dateFormat = DateFormat("yyyy-MM-dd");
|
final dateFormat = DateFormat("yyyy-MM-dd");
|
||||||
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
late PackageInfo kPackageInfo;
|
|
||||||
|
|
||||||
class SnackBarActionConfig {
|
class SnackBarActionConfig {
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'package:dio/dio.dart';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
||||||
|
|
||||||
class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||||
final BaseClient client;
|
final Dio client;
|
||||||
|
|
||||||
PaperlessAuthenticationApiImpl(this.client);
|
PaperlessAuthenticationApiImpl(this.client);
|
||||||
|
|
||||||
@@ -18,8 +15,8 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
|||||||
late Response response;
|
late Response response;
|
||||||
try {
|
try {
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
Uri.parse("/api/token/"),
|
"/api/token/",
|
||||||
body: {
|
data: {
|
||||||
"username": username,
|
"username": username,
|
||||||
"password": password,
|
"password": password,
|
||||||
},
|
},
|
||||||
@@ -34,11 +31,11 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
return response.data['token'];
|
||||||
return data['token'];
|
} else if (response.statusCode == 400 &&
|
||||||
} else if (response.statusCode == HttpStatus.badRequest &&
|
response
|
||||||
response.body
|
.data //TODO: Check if text is included in statusMessage instead of body
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains("no required certificate was sent")) {
|
.contains("no required certificate was sent")) {
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ abstract class PaperlessDocumentsApi {
|
|||||||
Uint8List documentBytes, {
|
Uint8List documentBytes, {
|
||||||
required String filename,
|
required String filename,
|
||||||
required String title,
|
required String title,
|
||||||
required String authToken,
|
DateTime? createdAt,
|
||||||
required String serverUrl,
|
|
||||||
int? documentType,
|
int? documentType,
|
||||||
int? correspondent,
|
int? correspondent,
|
||||||
Iterable<int> tags = const [],
|
Iterable<int> tags = const [],
|
||||||
DateTime? createdAt,
|
|
||||||
});
|
});
|
||||||
Future<DocumentModel> update(DocumentModel doc);
|
Future<DocumentModel> update(DocumentModel doc);
|
||||||
Future<int> findNextAsn();
|
Future<int> findNextAsn();
|
||||||
|
|||||||
@@ -1,94 +1,42 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:http/src/boundary_characters.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_api/src/constants.dart';
|
import 'package:paperless_api/src/constants.dart';
|
||||||
import 'package:paperless_api/src/models/bulk_edit_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/document_filter.dart';
|
|
||||||
import 'package:paperless_api/src/models/document_meta_data_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/document_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/paged_search_result.dart';
|
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
|
||||||
import 'package:paperless_api/src/models/query_parameters/sort_field.dart';
|
|
||||||
import 'package:paperless_api/src/models/query_parameters/sort_order.dart';
|
|
||||||
import 'package:paperless_api/src/models/similar_document_model.dart';
|
|
||||||
import 'paperless_documents_api.dart';
|
|
||||||
|
|
||||||
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||||
final BaseClient baseClient;
|
final Dio client;
|
||||||
final HttpClient httpClient;
|
|
||||||
|
|
||||||
PaperlessDocumentsApiImpl(this.baseClient, this.httpClient);
|
PaperlessDocumentsApiImpl(this.client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> create(
|
Future<void> create(
|
||||||
Uint8List documentBytes, {
|
Uint8List documentBytes, {
|
||||||
required String filename,
|
required String filename,
|
||||||
required String title,
|
required String title,
|
||||||
required String authToken,
|
DateTime? createdAt,
|
||||||
required String serverUrl,
|
|
||||||
int? documentType,
|
int? documentType,
|
||||||
int? correspondent,
|
int? correspondent,
|
||||||
Iterable<int> tags = const [],
|
Iterable<int> tags = const [],
|
||||||
DateTime? createdAt,
|
|
||||||
}) async {
|
}) async {
|
||||||
// The multipart request has to be generated from scratch as the http library does
|
final formData = FormData();
|
||||||
// not allow the same key (tags) to be added multiple times. However, this is what the
|
|
||||||
// paperless api expects, i.e. one block for each tag.
|
|
||||||
final request = await httpClient.postUrl(
|
|
||||||
Uri.parse("$serverUrl/api/documents/post_document/"),
|
|
||||||
);
|
|
||||||
|
|
||||||
final boundary = _boundaryString();
|
formData.fields.add(MapEntry('title', title));
|
||||||
|
|
||||||
StringBuffer bodyBuffer = StringBuffer();
|
|
||||||
|
|
||||||
var fields = <String, String>{};
|
|
||||||
fields.putIfAbsent('title', () => title);
|
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
fields.putIfAbsent('created', () => apiDateFormat.format(createdAt));
|
formData.fields.add(MapEntry('created', apiDateFormat.format(createdAt)));
|
||||||
}
|
}
|
||||||
if (correspondent != null) {
|
if (correspondent != null) {
|
||||||
fields.putIfAbsent('correspondent', () => jsonEncode(correspondent));
|
formData.fields.add(MapEntry('correspondent', jsonEncode(correspondent)));
|
||||||
}
|
}
|
||||||
if (documentType != null) {
|
if (documentType != null) {
|
||||||
fields.putIfAbsent('document_type', () => jsonEncode(documentType));
|
formData.fields.add(MapEntry('document_type', jsonEncode(documentType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final key in fields.keys) {
|
|
||||||
bodyBuffer.write(_buildMultipartField(key, fields[key]!, boundary));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final tag in tags) {
|
for (final tag in tags) {
|
||||||
bodyBuffer.write(_buildMultipartField('tags', tag.toString(), boundary));
|
formData.fields.add(MapEntry('tags', tag.toString()));
|
||||||
}
|
}
|
||||||
|
final response =
|
||||||
bodyBuffer.write("--$boundary"
|
await client.post('/api/documents/post_document/', data: formData);
|
||||||
'\r\nContent-Disposition: form-data; name="document"; filename="$filename"'
|
|
||||||
"\r\nContent-type: application/octet-stream"
|
|
||||||
"\r\n\r\n");
|
|
||||||
|
|
||||||
final closing = "\r\n--$boundary--\r\n";
|
|
||||||
|
|
||||||
// Set headers
|
|
||||||
request.headers.set(HttpHeaders.contentTypeHeader,
|
|
||||||
"multipart/form-data; boundary=$boundary");
|
|
||||||
request.headers.set(HttpHeaders.contentLengthHeader,
|
|
||||||
"${bodyBuffer.length + closing.length + documentBytes.lengthInBytes}");
|
|
||||||
request.headers.set(HttpHeaders.authorizationHeader, "Token $authToken");
|
|
||||||
|
|
||||||
//Write fields to request
|
|
||||||
request.write(bodyBuffer.toString());
|
|
||||||
//Stream file
|
|
||||||
await request.addStream(Stream.fromIterable(documentBytes.map((e) => [e])));
|
|
||||||
// Write closing boundary to request
|
|
||||||
request.write(closing);
|
|
||||||
|
|
||||||
final response = await request.close();
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.documentUploadFailed,
|
ErrorCode.documentUploadFailed,
|
||||||
@@ -97,38 +45,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _buildMultipartField(String fieldName, String value, String boundary) {
|
|
||||||
// ignore: prefer_interpolation_to_compose_strings
|
|
||||||
return '--$boundary'
|
|
||||||
'\r\nContent-Disposition: form-data; name="$fieldName"'
|
|
||||||
'\r\nContent-type: text/plain'
|
|
||||||
'\r\n\r\n' +
|
|
||||||
value +
|
|
||||||
'\r\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
String _boundaryString() {
|
|
||||||
Random _random = Random();
|
|
||||||
var prefix = 'dart-http-boundary-';
|
|
||||||
var list = List<int>.generate(
|
|
||||||
70 - prefix.length,
|
|
||||||
(index) => boundaryCharacters[_random.nextInt(boundaryCharacters.length)],
|
|
||||||
growable: false,
|
|
||||||
);
|
|
||||||
return '$prefix${String.fromCharCodes(list)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentModel> update(DocumentModel doc) async {
|
Future<DocumentModel> update(DocumentModel doc) async {
|
||||||
final response = await baseClient.put(
|
final response = await client.put(
|
||||||
Uri.parse("/api/documents/${doc.id}/"),
|
"/api/documents/${doc.id}/",
|
||||||
body: json.encode(doc.toJson()),
|
data: doc.toJson(),
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return DocumentModel.fromJson(
|
return DocumentModel.fromJson(response.data);
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
||||||
}
|
}
|
||||||
@@ -137,17 +61,15 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
@override
|
@override
|
||||||
Future<PagedSearchResult<DocumentModel>> find(DocumentFilter filter) async {
|
Future<PagedSearchResult<DocumentModel>> find(DocumentFilter filter) async {
|
||||||
final filterParams = filter.toQueryParameters();
|
final filterParams = filter.toQueryParameters();
|
||||||
final response = await baseClient.get(
|
final response = await client.get(
|
||||||
Uri(
|
"/api/documents/",
|
||||||
path: "/api/documents/",
|
|
||||||
queryParameters: filterParams,
|
queryParameters: filterParams,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return compute(
|
return compute(
|
||||||
PagedSearchResult.fromJson,
|
PagedSearchResult.fromJson,
|
||||||
PagedSearchResultJsonSerializer<DocumentModel>(
|
PagedSearchResultJsonSerializer<DocumentModel>(
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
response.data,
|
||||||
DocumentModel.fromJson,
|
DocumentModel.fromJson,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -158,8 +80,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(DocumentModel doc) async {
|
Future<int> delete(DocumentModel doc) async {
|
||||||
final response =
|
final response = await client.delete("/api/documents/${doc.id}/");
|
||||||
await baseClient.delete(Uri.parse("/api/documents/${doc.id}/"));
|
|
||||||
|
|
||||||
if (response.statusCode == 204) {
|
if (response.statusCode == 204) {
|
||||||
return Future.value(doc.id);
|
return Future.value(doc.id);
|
||||||
@@ -178,9 +99,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> getPreview(int documentId) async {
|
Future<Uint8List> getPreview(int documentId) async {
|
||||||
final response = await baseClient.get(Uri.parse(getPreviewUrl(documentId)));
|
final response = await client.get(
|
||||||
|
getPreviewUrl(documentId),
|
||||||
|
options: Options(
|
||||||
|
responseType:
|
||||||
|
ResponseType.bytes), //TODO: Check if bytes or stream is required
|
||||||
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return response.bodyBytes;
|
return response.data;
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
||||||
}
|
}
|
||||||
@@ -207,10 +133,9 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
||||||
final response = await baseClient.post(
|
final response = await client.post(
|
||||||
Uri.parse("/api/documents/bulk_edit/"),
|
"/api/documents/bulk_edit/",
|
||||||
body: json.encode(action.toJson()),
|
data: action.toJson(),
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return action.documentIds;
|
return action.documentIds;
|
||||||
@@ -241,40 +166,48 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> download(DocumentModel document) async {
|
Future<Uint8List> download(DocumentModel document) async {
|
||||||
final response = await baseClient
|
//TODO: Add missing error handling
|
||||||
.get(Uri.parse("/api/documents/${document.id}/download/"));
|
final response = await client.get(
|
||||||
return response.bodyBytes;
|
"/api/documents/${document.id}/download/",
|
||||||
|
options: Options(responseType: ResponseType.bytes),
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
||||||
final response = await baseClient
|
final response =
|
||||||
.get(Uri.parse("/api/documents/${document.id}/metadata/"));
|
await client.get("/api/documents/${document.id}/metadata/");
|
||||||
return compute(
|
return compute(
|
||||||
DocumentMetaData.fromJson,
|
DocumentMetaData.fromJson,
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
response.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> autocomplete(String query, [int limit = 10]) async {
|
Future<List<String>> autocomplete(String query, [int limit = 10]) async {
|
||||||
final response = await baseClient
|
final response = await client.get(
|
||||||
.get(Uri.parse("/api/search/autocomplete/?query=$query&limit=$limit}"));
|
'/api/search/autocomplete/',
|
||||||
|
queryParameters: {
|
||||||
|
'query': query,
|
||||||
|
'limit': limit,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return jsonDecode(utf8.decode(response.bodyBytes)) as List<String>;
|
return response.data as List<String>;
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<SimilarDocumentModel>> findSimilar(int docId) async {
|
Future<List<SimilarDocumentModel>> findSimilar(int docId) async {
|
||||||
final response = await baseClient
|
final response =
|
||||||
.get(Uri.parse("/api/documents/?more_like=$docId&pageSize=10"));
|
await client.get("/api/documents/?more_like=$docId&pageSize=10");
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return (await compute(
|
return (await compute(
|
||||||
PagedSearchResult<SimilarDocumentModel>.fromJson,
|
PagedSearchResult<SimilarDocumentModel>.fromJson,
|
||||||
PagedSearchResultJsonSerializer(
|
PagedSearchResultJsonSerializer(
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
response.data,
|
||||||
SimilarDocumentModel.fromJson,
|
SimilarDocumentModel.fromJson,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
||||||
import 'package:paperless_api/src/utils.dart';
|
import 'package:paperless_api/src/request_utils.dart';
|
||||||
|
|
||||||
|
//Notes:
|
||||||
|
// Removed content type json header
|
||||||
class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||||
final BaseClient client;
|
final Dio client;
|
||||||
|
|
||||||
PaperlessLabelApiImpl(this.client);
|
PaperlessLabelApiImpl(this.client);
|
||||||
@override
|
@override
|
||||||
@@ -89,15 +91,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<Correspondent> saveCorrespondent(Correspondent correspondent) async {
|
Future<Correspondent> saveCorrespondent(Correspondent correspondent) async {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('/api/correspondents/'),
|
'/api/correspondents/',
|
||||||
body: jsonEncode(correspondent.toJson()),
|
data: correspondent.toJson(),
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return Correspondent.fromJson(
|
return Correspondent.fromJson(response.data);
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.correspondentCreateFailed,
|
ErrorCode.correspondentCreateFailed,
|
||||||
@@ -108,15 +106,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<DocumentType> saveDocumentType(DocumentType type) async {
|
Future<DocumentType> saveDocumentType(DocumentType type) async {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('/api/document_types/'),
|
'/api/document_types/',
|
||||||
body: json.encode(type.toJson()),
|
data: type.toJson(),
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return DocumentType.fromJson(
|
return DocumentType.fromJson(response.data);
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.documentTypeCreateFailed,
|
ErrorCode.documentTypeCreateFailed,
|
||||||
@@ -126,18 +120,13 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Tag> saveTag(Tag tag) async {
|
Future<Tag> saveTag(Tag tag) async {
|
||||||
final body = json.encode(tag.toJson());
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('/api/tags/'),
|
'/api/tags/',
|
||||||
body: body,
|
data: tag.toJson(),
|
||||||
headers: {
|
options: Options(headers: {"Accept": "application/json; version=2"}),
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json; version=2",
|
|
||||||
},
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return Tag.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return Tag.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.tagCreateFailed,
|
ErrorCode.tagCreateFailed,
|
||||||
@@ -148,8 +137,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
||||||
assert(correspondent.id != null);
|
assert(correspondent.id != null);
|
||||||
final response = await client
|
final response =
|
||||||
.delete(Uri.parse('/api/correspondents/${correspondent.id}/'));
|
await client.delete('/api/correspondents/${correspondent.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return correspondent.id!;
|
return correspondent.id!;
|
||||||
}
|
}
|
||||||
@@ -162,8 +151,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<int> deleteDocumentType(DocumentType documentType) async {
|
Future<int> deleteDocumentType(DocumentType documentType) async {
|
||||||
assert(documentType.id != null);
|
assert(documentType.id != null);
|
||||||
final response = await client
|
final response =
|
||||||
.delete(Uri.parse('/api/document_types/${documentType.id}/'));
|
await client.delete('/api/document_types/${documentType.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return documentType.id!;
|
return documentType.id!;
|
||||||
}
|
}
|
||||||
@@ -176,7 +165,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<int> deleteTag(Tag tag) async {
|
Future<int> deleteTag(Tag tag) async {
|
||||||
assert(tag.id != null);
|
assert(tag.id != null);
|
||||||
final response = await client.delete(Uri.parse('/api/tags/${tag.id}/'));
|
final response = await client.delete('/api/tags/${tag.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return tag.id!;
|
return tag.id!;
|
||||||
}
|
}
|
||||||
@@ -190,17 +179,14 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
||||||
assert(correspondent.id != null);
|
assert(correspondent.id != null);
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('/api/correspondents/${correspondent.id}/'),
|
'/api/correspondents/${correspondent.id}/',
|
||||||
headers: {"Content-Type": "application/json"},
|
data: json.encode(correspondent.toJson()),
|
||||||
body: json.encode(correspondent.toJson()),
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return Correspondent.fromJson(
|
return Correspondent.fromJson(response.data);
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)));
|
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.unknown,
|
ErrorCode.unknown, //TODO: Add correct error code mapping.
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -209,13 +195,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
||||||
assert(documentType.id != null);
|
assert(documentType.id != null);
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('/api/document_types/${documentType.id}/'),
|
'/api/document_types/${documentType.id}/',
|
||||||
headers: {"Content-Type": "application/json"},
|
data: documentType.toJson(),
|
||||||
body: json.encode(documentType.toJson()),
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return DocumentType.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return DocumentType.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
@@ -227,16 +211,12 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<Tag> updateTag(Tag tag) async {
|
Future<Tag> updateTag(Tag tag) async {
|
||||||
assert(tag.id != null);
|
assert(tag.id != null);
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('/api/tags/${tag.id}/'),
|
'/api/tags/${tag.id}/',
|
||||||
headers: {
|
options: Options(headers: {"Accept": "application/json; version=2"}),
|
||||||
"Accept": "application/json; version=2",
|
data: tag.toJson(),
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: json.encode(tag.toJson()),
|
|
||||||
encoding: Encoding.getByName("utf-8"),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return Tag.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return Tag.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
@@ -247,8 +227,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<int> deleteStoragePath(StoragePath path) async {
|
Future<int> deleteStoragePath(StoragePath path) async {
|
||||||
assert(path.id != null);
|
assert(path.id != null);
|
||||||
final response =
|
final response = await client.delete('/api/storage_paths/${path.id}/');
|
||||||
await client.delete(Uri.parse('/api/storage_paths/${path.id}/'));
|
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return path.id!;
|
return path.id!;
|
||||||
}
|
}
|
||||||
@@ -285,12 +264,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
@override
|
@override
|
||||||
Future<StoragePath> saveStoragePath(StoragePath path) async {
|
Future<StoragePath> saveStoragePath(StoragePath path) async {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('/api/storage_paths/'),
|
'/api/storage_paths/',
|
||||||
body: json.encode(path.toJson()),
|
data: path.toJson(),
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return StoragePath.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return StoragePath.fromJson(jsonDecode(response.data));
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(ErrorCode.storagePathCreateFailed,
|
throw PaperlessServerException(ErrorCode.storagePathCreateFailed,
|
||||||
httpStatusCode: response.statusCode);
|
httpStatusCode: response.statusCode);
|
||||||
@@ -300,12 +278,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<StoragePath> updateStoragePath(StoragePath path) async {
|
Future<StoragePath> updateStoragePath(StoragePath path) async {
|
||||||
assert(path.id != null);
|
assert(path.id != null);
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('/api/storage_paths/${path.id}/'),
|
'/api/storage_paths/${path.id}/',
|
||||||
headers: {"Content-Type": "application/json"},
|
data: path.toJson(),
|
||||||
body: json.encode(path.toJson()),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return StoragePath.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return StoragePath.fromJson(jsonDecode(response.data));
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.unknown);
|
throw const PaperlessServerException(ErrorCode.unknown);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
import 'package:paperless_api/src/models/saved_view_model.dart';
|
import 'package:paperless_api/src/models/saved_view_model.dart';
|
||||||
import 'package:paperless_api/src/utils.dart';
|
import 'package:paperless_api/src/request_utils.dart';
|
||||||
|
|
||||||
import 'paperless_saved_views_api.dart';
|
import 'paperless_saved_views_api.dart';
|
||||||
|
|
||||||
class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||||
final BaseClient client;
|
final Dio client;
|
||||||
|
|
||||||
PaperlessSavedViewsApiImpl(this.client);
|
PaperlessSavedViewsApiImpl(this.client);
|
||||||
|
|
||||||
@@ -28,12 +28,11 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
@override
|
@override
|
||||||
Future<SavedView> save(SavedView view) async {
|
Future<SavedView> save(SavedView view) async {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse("/api/saved_views/"),
|
"/api/saved_views/",
|
||||||
body: jsonEncode(view.toJson()),
|
data: view.toJson(),
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return SavedView.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
return SavedView.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.createSavedViewError,
|
ErrorCode.createSavedViewError,
|
||||||
@@ -43,8 +42,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(SavedView view) async {
|
Future<int> delete(SavedView view) async {
|
||||||
final response =
|
final response = await client.delete("/api/saved_views/${view.id}/");
|
||||||
await client.delete(Uri.parse("/api/saved_views/${view.id}/"));
|
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return view.id!;
|
return view.id!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_information_model.dart';
|
import 'package:paperless_api/src/models/paperless_server_information_model.dart';
|
||||||
@@ -13,25 +14,24 @@ import 'paperless_server_stats_api.dart';
|
|||||||
/// inbox and total number of documents.
|
/// inbox and total number of documents.
|
||||||
///
|
///
|
||||||
class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
||||||
final BaseClient client;
|
final Dio client;
|
||||||
|
|
||||||
PaperlessServerStatsApiImpl(this.client);
|
PaperlessServerStatsApiImpl(this.client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaperlessServerInformationModel> getServerInformation() async {
|
Future<PaperlessServerInformationModel> getServerInformation() async {
|
||||||
final response = await client.get(Uri.parse("/api/ui_settings/"));
|
final response = await client.get("/api/ui_settings/");
|
||||||
final version =
|
final version = response
|
||||||
response.headers[PaperlessServerInformationModel.versionHeader] ??
|
.headers[PaperlessServerInformationModel.versionHeader]?.first ??
|
||||||
'unknown';
|
'unknown';
|
||||||
final apiVersion = int.tryParse(
|
final apiVersion = int.tryParse(response
|
||||||
response.headers[PaperlessServerInformationModel.apiVersionHeader] ??
|
.headers[PaperlessServerInformationModel.apiVersionHeader]?.first ??
|
||||||
'1');
|
'1');
|
||||||
final String username =
|
final String username = response.data['username'];
|
||||||
jsonDecode(utf8.decode(response.bodyBytes))['username'];
|
|
||||||
final String host = response
|
final String host = response
|
||||||
.headers[PaperlessServerInformationModel.hostHeader] ??
|
.headers[PaperlessServerInformationModel.hostHeader]?.first ??
|
||||||
response.request?.headers[PaperlessServerInformationModel.hostHeader] ??
|
response.headers[PaperlessServerInformationModel.hostHeader]?.first ??
|
||||||
('${response.request?.url.host}:${response.request?.url.port}');
|
('${response.requestOptions.uri.host}:${response.requestOptions.uri.port}');
|
||||||
return PaperlessServerInformationModel(
|
return PaperlessServerInformationModel(
|
||||||
username: username,
|
username: username,
|
||||||
version: version,
|
version: version,
|
||||||
@@ -42,11 +42,9 @@ class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaperlessServerStatisticsModel> getServerStatistics() async {
|
Future<PaperlessServerStatisticsModel> getServerStatistics() async {
|
||||||
final response = await client.get(Uri.parse('/api/statistics/'));
|
final response = await client.get('/api/statistics/');
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return PaperlessServerStatisticsModel.fromJson(
|
return PaperlessServerStatisticsModel.fromJson(response.data);
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
throw const PaperlessServerException.unknown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
|
|
||||||
Future<T> getSingleResult<T>(
|
Future<T> getSingleResult<T>(
|
||||||
String url,
|
String url,
|
||||||
T Function(Map<String, dynamic>) fromJson,
|
T Function(Map<String, dynamic>) fromJson,
|
||||||
ErrorCode errorCode, {
|
ErrorCode errorCode, {
|
||||||
required BaseClient client,
|
required Dio client,
|
||||||
int minRequiredApiVersion = 1,
|
int minRequiredApiVersion = 1,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
Uri.parse(url),
|
url,
|
||||||
|
options: Options(
|
||||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
return compute(
|
return compute(
|
||||||
fromJson,
|
fromJson,
|
||||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
response.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
@@ -32,16 +33,17 @@ Future<List<T>> getCollection<T>(
|
|||||||
String url,
|
String url,
|
||||||
T Function(Map<String, dynamic>) fromJson,
|
T Function(Map<String, dynamic>) fromJson,
|
||||||
ErrorCode errorCode, {
|
ErrorCode errorCode, {
|
||||||
required BaseClient client,
|
required Dio client,
|
||||||
int minRequiredApiVersion = 1,
|
int minRequiredApiVersion = 1,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
Uri.parse(url),
|
url,
|
||||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
options: Options(headers: {
|
||||||
|
'accept': 'application/json; version=$minRequiredApiVersion'
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
final Map<String, dynamic> body =
|
final Map<String, dynamic> body = response.data;
|
||||||
jsonDecode(utf8.decode(response.bodyBytes));
|
|
||||||
if (body.containsKey('count')) {
|
if (body.containsKey('count')) {
|
||||||
if (body['count'] == 0) {
|
if (body['count'] == 0) {
|
||||||
return <T>[];
|
return <T>[];
|
||||||
@@ -17,6 +17,7 @@ dependencies:
|
|||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
|
dio: ^4.0.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
40
pubspec.lock
40
pubspec.lock
@@ -385,6 +385,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.1"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -665,7 +673,7 @@ packages:
|
|||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
get_it:
|
get_it:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: get_it
|
name: get_it
|
||||||
sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7"
|
sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7"
|
||||||
@@ -736,6 +744,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
hydrated_bloc:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hydrated_bloc
|
||||||
|
sha256: "5871204f14b24638dc9d18d5b94cf22a66fc4be40756925cafff3a7553c7d7b7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.0"
|
||||||
image:
|
image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -753,7 +769,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.0"
|
||||||
injectable:
|
injectable:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: injectable
|
name: injectable
|
||||||
sha256: "7dab7d341feb40a0590d9ff6261aea9495522005e2c6763f9161a4face916f7b"
|
sha256: "7dab7d341feb40a0590d9ff6261aea9495522005e2c6763f9161a4face916f7b"
|
||||||
@@ -814,13 +830,21 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.5"
|
version: "0.6.5"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c"
|
sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "4.7.0"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
sha256: f3c2c18a7889580f71926f30c1937727c8c7d4f3a435f8f5e8b0ddd25253ef5d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.4"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1221,7 +1245,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "4.2.4"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
||||||
@@ -1417,6 +1441,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.6"
|
version: "1.2.6"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
get_it: ^7.2.0
|
|
||||||
injectable: ^2.1.0
|
|
||||||
encrypted_shared_preferences: ^3.0.0
|
encrypted_shared_preferences: ^3.0.0
|
||||||
permission_handler: ^9.2.0
|
permission_handler: ^9.2.0
|
||||||
pdf: ^3.8.1
|
pdf: ^3.8.1
|
||||||
@@ -79,6 +77,11 @@ dependencies:
|
|||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
badges: ^2.0.3
|
badges: ^2.0.3
|
||||||
flutter_colorpicker: ^1.0.3
|
flutter_colorpicker: ^1.0.3
|
||||||
|
provider: ^6.0.5
|
||||||
|
dio: ^4.0.6
|
||||||
|
hydrated_bloc: ^9.0.0
|
||||||
|
json_annotation: ^4.7.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
integration_test:
|
integration_test:
|
||||||
@@ -92,6 +95,7 @@ dev_dependencies:
|
|||||||
dependency_validator: ^3.0.0
|
dependency_validator: ^3.0.0
|
||||||
intl_utils: ^2.7.0
|
intl_utils: ^2.7.0
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
json_serializable: ^6.5.4
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
Reference in New Issue
Block a user