mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 13:15:55 -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_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/di_test_mocks.mocks.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
// import 'package:mockito/mockito.dart';
|
||||
// import 'package:paperless_api/paperless_api.dart';
|
||||
// import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
// import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||
// import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
// import 'package:paperless_mobile/di_test_mocks.mocks.dart';
|
||||
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
// import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
// import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
import 'src/framework.dart';
|
||||
// import 'src/framework.dart';
|
||||
|
||||
void main() async {
|
||||
final t = await initializeTestingFramework(languageCode: 'de');
|
||||
// void main() async {
|
||||
// final t = await initializeTestingFramework(languageCode: 'de');
|
||||
|
||||
const testServerUrl = 'https://example.com';
|
||||
const testUsername = 'user';
|
||||
const testPassword = 'pass';
|
||||
// const testServerUrl = 'https://example.com';
|
||||
// const testUsername = 'user';
|
||||
// const testPassword = 'pass';
|
||||
|
||||
final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
||||
final usernameField = find.byKey(const ValueKey('login-username'));
|
||||
final passwordField = find.byKey(const ValueKey('login-password'));
|
||||
final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
||||
// final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
||||
// final usernameField = find.byKey(const ValueKey('login-username'));
|
||||
// final passwordField = find.byKey(const ValueKey('login-password'));
|
||||
// final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
||||
|
||||
testWidgets('Test successful login flow', (WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
// Initialize dat for mocked classes
|
||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
when(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
)).thenAnswer((i) => Future.value("eyTestToken"));
|
||||
// testWidgets('Test successful login flow', (WidgetTester tester) async {
|
||||
// await initAndLaunchTestApp(tester, () async {
|
||||
// // Initialize dat for mocked classes
|
||||
// when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
// .thenAnswer((i) => Stream.value(true));
|
||||
// when((getIt<LocalVault>() as MockLocalVault)
|
||||
// .loadAuthenticationInformation())
|
||||
// .thenAnswer((realInvocation) async => null);
|
||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
// preferredLocaleSubtag: 'en',
|
||||
// preferredThemeMode: ThemeMode.light,
|
||||
// isLocalAuthenticationEnabled: false,
|
||||
// preferredViewType: ViewType.list,
|
||||
// showInboxOnStartup: false,
|
||||
// ));
|
||||
// when(getIt<PaperlessAuthenticationApi>().login(
|
||||
// username: testUsername,
|
||||
// password: testPassword,
|
||||
// )).thenAnswer((i) => Future.value("eyTestToken"));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
});
|
||||
// await getIt<ConnectivityCubit>().initialize();
|
||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||
// });
|
||||
|
||||
// Mocked classes
|
||||
// // Mocked classes
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
// await t.binding.waitUntilFirstFrameRasterized;
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(serverAddressField, testServerUrl);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(usernameField, testUsername);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
// await tester.enterText(passwordField, testPassword);
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
// FocusManager.instance.primaryFocus?.unfocus();
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
// await tester.tap(loginBtn);
|
||||
|
||||
verify(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
)).called(1);
|
||||
});
|
||||
// verify(getIt<PaperlessAuthenticationApi>().login(
|
||||
// username: testUsername,
|
||||
// password: testPassword,
|
||||
// )).called(1);
|
||||
// });
|
||||
|
||||
testWidgets('Test login validation missing password',
|
||||
(WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
.connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
// testWidgets('Test login validation missing password',
|
||||
// (WidgetTester tester) async {
|
||||
// await initAndLaunchTestApp(tester, () async {
|
||||
// when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
// .connectivityChanges())
|
||||
// .thenAnswer((i) => Stream.value(true));
|
||||
// when((getIt<LocalVault>() as MockLocalVault)
|
||||
// .loadAuthenticationInformation())
|
||||
// .thenAnswer((realInvocation) async => null);
|
||||
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
// preferredLocaleSubtag: 'en',
|
||||
// preferredThemeMode: ThemeMode.light,
|
||||
// isLocalAuthenticationEnabled: false,
|
||||
// preferredViewType: ViewType.list,
|
||||
// showInboxOnStartup: false,
|
||||
// ));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
});
|
||||
// Mocked classes
|
||||
// await getIt<ConnectivityCubit>().initialize();
|
||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||
// });
|
||||
// // Mocked classes
|
||||
|
||||
// Initialize dat for mocked classes
|
||||
// // Initialize dat for mocked classes
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
// await t.binding.waitUntilFirstFrameRasterized;
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(serverAddressField, testServerUrl);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(usernameField, testUsername);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
// FocusManager.instance.primaryFocus?.unfocus();
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.tap(loginBtn);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
.login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(t.translations.loginPagePasswordValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
// verifyNever(
|
||||
// (getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
// .login(
|
||||
// username: testUsername,
|
||||
// password: testPassword,
|
||||
// ));
|
||||
// expect(
|
||||
// find.textContaining(t.translations.loginPagePasswordValidatorMessageText),
|
||||
// findsOneWidget,
|
||||
// );
|
||||
// });
|
||||
|
||||
testWidgets('Test login validation missing username',
|
||||
(WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
.connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
});
|
||||
// testWidgets('Test login validation missing username',
|
||||
// (WidgetTester tester) async {
|
||||
// await initAndLaunchTestApp(tester, () async {
|
||||
// when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
// .connectivityChanges())
|
||||
// .thenAnswer((i) => Stream.value(true));
|
||||
// when((getIt<LocalVault>() as MockLocalVault)
|
||||
// .loadAuthenticationInformation())
|
||||
// .thenAnswer((realInvocation) async => null);
|
||||
// when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
// preferredLocaleSubtag: 'en',
|
||||
// preferredThemeMode: ThemeMode.light,
|
||||
// isLocalAuthenticationEnabled: false,
|
||||
// preferredViewType: ViewType.list,
|
||||
// showInboxOnStartup: false,
|
||||
// ));
|
||||
// await getIt<ConnectivityCubit>().initialize();
|
||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||
// });
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
// await t.binding.waitUntilFirstFrameRasterized;
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(serverAddressField, testServerUrl);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(passwordField, testPassword);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
// FocusManager.instance.primaryFocus?.unfocus();
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.tap(loginBtn);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
.login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(t.translations.loginPageUsernameValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
// verifyNever(
|
||||
// (getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
// .login(
|
||||
// username: testUsername,
|
||||
// password: testPassword,
|
||||
// ));
|
||||
// expect(
|
||||
// find.textContaining(t.translations.loginPageUsernameValidatorMessageText),
|
||||
// findsOneWidget,
|
||||
// );
|
||||
// });
|
||||
|
||||
testWidgets('Test login validation missing server address',
|
||||
(WidgetTester tester) async {
|
||||
initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
// testWidgets('Test login validation missing server address',
|
||||
// (WidgetTester tester) async {
|
||||
// initAndLaunchTestApp(tester, () async {
|
||||
// when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
// .thenAnswer((i) => Stream.value(true));
|
||||
|
||||
when((getIt<LocalVault>()).loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
// when((getIt<LocalVault>()).loadAuthenticationInformation())
|
||||
// .thenAnswer((realInvocation) async => null);
|
||||
|
||||
when((getIt<LocalVault>()).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
// when((getIt<LocalVault>()).loadApplicationSettings())
|
||||
// .thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
// preferredLocaleSubtag: 'en',
|
||||
// preferredThemeMode: ThemeMode.light,
|
||||
// isLocalAuthenticationEnabled: false,
|
||||
// preferredViewType: ViewType.list,
|
||||
// showInboxOnStartup: false,
|
||||
// ));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
});
|
||||
// await getIt<ConnectivityCubit>().initialize();
|
||||
// await getIt<ApplicationSettingsCubit>().initialize();
|
||||
// });
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
// await t.binding.waitUntilFirstFrameRasterized;
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(usernameField, testUsername);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.enterText(passwordField, testPassword);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
// FocusManager.instance.primaryFocus?.unfocus();
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
// await tester.tap(loginBtn);
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(
|
||||
t.translations.loginPageServerUrlValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
// verifyNever(getIt<PaperlessAuthenticationApi>().login(
|
||||
// username: testUsername,
|
||||
// password: testPassword,
|
||||
// ));
|
||||
// expect(
|
||||
// find.textContaining(
|
||||
// t.translations.loginPageServerUrlValidatorMessageText),
|
||||
// findsOneWidget,
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
Future<TestingFrameworkVariables> initializeTestingFramework(
|
||||
{String languageCode = 'en'}) async {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
configureDependencies('test');
|
||||
final translations = await S.load(
|
||||
Locale.fromSubtags(
|
||||
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:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
|
||||
@prod
|
||||
@injectable
|
||||
class AuthenticationInterceptor implements InterceptorContract {
|
||||
final LocalVault _localVault;
|
||||
AuthenticationInterceptor(this._localVault);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
|
||||
@prod
|
||||
@injectable
|
||||
class BaseUrlInterceptor implements InterceptorContract {
|
||||
final LocalVault _localVault;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@injectable
|
||||
class LanguageHeaderInterceptor implements InterceptorContract {
|
||||
final ApplicationSettingsCubit appSettingsCubit;
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
const interceptedRoutes = ['thumb/'];
|
||||
|
||||
@injectable
|
||||
@prod
|
||||
class ResponseConversionInterceptor implements InterceptorContract {
|
||||
@override
|
||||
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) {
|
||||
return MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
RepositoryProvider(
|
||||
create: (context) =>
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
RepositoryProvider(
|
||||
create: (context) =>
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
RepositoryProvider(
|
||||
create: (context) =>
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
RepositoryProvider(
|
||||
create: (context) =>
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
],
|
||||
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 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
abstract class ConnectivityStatusService {
|
||||
Future<bool> isConnectedToInternet();
|
||||
@@ -9,8 +8,6 @@ abstract class ConnectivityStatusService {
|
||||
Stream<bool> connectivityChanges();
|
||||
}
|
||||
|
||||
@prod
|
||||
@Injectable(as: ConnectivityStatusService)
|
||||
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
||||
final Connectivity connectivity;
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ class FileService {
|
||||
static Future<void> clearUserData() async {
|
||||
final scanDir = await scanDirectory;
|
||||
final tempDir = await temporaryDirectory;
|
||||
scanDir?.delete(recursive: true);
|
||||
tempDir.delete(recursive: true);
|
||||
await scanDir?.delete(recursive: true);
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.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/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
@@ -17,8 +15,6 @@ abstract class StatusService {
|
||||
AuthenticationInformation credentials, String documentFileName);
|
||||
}
|
||||
|
||||
@Singleton(as: StatusService)
|
||||
@Named("webSocketStatusService")
|
||||
class WebSocketStatusService implements StatusService {
|
||||
late WebSocket? socket;
|
||||
late IOWebSocketChannel? _channel;
|
||||
@@ -31,35 +27,33 @@ class WebSocketStatusService implements StatusService {
|
||||
AuthenticationInformation credentials,
|
||||
String documentFileName,
|
||||
) async {
|
||||
socket = await WebSocket.connect(
|
||||
httpUrl.replaceFirst("http", "ws") + "/ws/status/",
|
||||
customClient: getIt<HttpClient>(),
|
||||
headers: {
|
||||
'Authorization': 'Token ${credentials.token}',
|
||||
},
|
||||
).catchError((_) {
|
||||
// Use long polling if connection could not be established
|
||||
});
|
||||
// socket = await WebSocket.connect(
|
||||
// httpUrl.replaceFirst("http", "ws") + "/ws/status/",
|
||||
// customClient: getIt<HttpClient>(),
|
||||
// headers: {
|
||||
// 'Authorization': 'Token ${credentials.token}',
|
||||
// },
|
||||
// ).catchError((_) {
|
||||
// // Use long polling if connection could not be established
|
||||
// });
|
||||
|
||||
if (socket != null) {
|
||||
socket!.where(isNotNull).listen((event) {
|
||||
final status = DocumentProcessingStatus.fromJson(event);
|
||||
getIt<DocumentStatusCubit>().updateStatus(status);
|
||||
if (status.currentProgress == 100) {
|
||||
socket!.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (socket != null) {
|
||||
// socket!.where(isNotNull).listen((event) {
|
||||
// final status = DocumentProcessingStatus.fromJson(event);
|
||||
// getIt<DocumentStatusCubit>().updateStatus(status);
|
||||
// if (status.currentProgress == 100) {
|
||||
// socket!.close();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable(as: StatusService)
|
||||
@Named("longPollingStatusService")
|
||||
class LongPollingStatusService implements StatusService {
|
||||
static const maxRetries = 60;
|
||||
|
||||
final BaseClient httpClient;
|
||||
LongPollingStatusService(@Named("timeoutClient") this.httpClient);
|
||||
LongPollingStatusService(this.httpClient);
|
||||
|
||||
@override
|
||||
Future<void> startListeningBeforeDocumentUpload(
|
||||
@@ -67,51 +61,51 @@ class LongPollingStatusService implements StatusService {
|
||||
AuthenticationInformation credentials,
|
||||
String documentFileName,
|
||||
) async {
|
||||
final today = DateTime.now();
|
||||
bool consumptionFinished = false;
|
||||
int retryCount = 0;
|
||||
// final today = DateTime.now();
|
||||
// bool consumptionFinished = false;
|
||||
// int retryCount = 0;
|
||||
|
||||
getIt<DocumentStatusCubit>().updateStatus(
|
||||
DocumentProcessingStatus(
|
||||
currentProgress: 0,
|
||||
filename: documentFileName,
|
||||
maxProgress: 100,
|
||||
message: ProcessingMessage.new_file,
|
||||
status: ProcessingStatus.working,
|
||||
taskId: DocumentProcessingStatus.unknownTaskId,
|
||||
documentId: null,
|
||||
isApproximated: true,
|
||||
),
|
||||
);
|
||||
// getIt<DocumentStatusCubit>().updateStatus(
|
||||
// DocumentProcessingStatus(
|
||||
// currentProgress: 0,
|
||||
// filename: documentFileName,
|
||||
// maxProgress: 100,
|
||||
// message: ProcessingMessage.new_file,
|
||||
// status: ProcessingStatus.working,
|
||||
// taskId: DocumentProcessingStatus.unknownTaskId,
|
||||
// documentId: null,
|
||||
// isApproximated: true,
|
||||
// ),
|
||||
// );
|
||||
|
||||
do {
|
||||
final response = await httpClient.get(
|
||||
Uri.parse(
|
||||
'$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'),
|
||||
);
|
||||
final data = await compute(
|
||||
PagedSearchResult.fromJson,
|
||||
PagedSearchResultJsonSerializer(
|
||||
jsonDecode(response.body), DocumentModel.fromJson),
|
||||
);
|
||||
if (data.count > 0) {
|
||||
consumptionFinished = true;
|
||||
final docId = data.results[0].id;
|
||||
getIt<DocumentStatusCubit>().updateStatus(
|
||||
DocumentProcessingStatus(
|
||||
currentProgress: 100,
|
||||
filename: documentFileName,
|
||||
maxProgress: 100,
|
||||
message: ProcessingMessage.finished,
|
||||
status: ProcessingStatus.success,
|
||||
taskId: DocumentProcessingStatus.unknownTaskId,
|
||||
documentId: docId,
|
||||
isApproximated: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
sleep(const Duration(seconds: 1));
|
||||
} while (!consumptionFinished && retryCount < maxRetries);
|
||||
// do {
|
||||
// final response = await httpClient.get(
|
||||
// Uri.parse(
|
||||
// '$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'),
|
||||
// );
|
||||
// final data = await compute(
|
||||
// PagedSearchResult.fromJson,
|
||||
// PagedSearchResultJsonSerializer(
|
||||
// jsonDecode(response.body), DocumentModel.fromJson),
|
||||
// );
|
||||
// if (data.count > 0) {
|
||||
// consumptionFinished = true;
|
||||
// final docId = data.results[0].id;
|
||||
// getIt<DocumentStatusCubit>().updateStatus(
|
||||
// DocumentProcessingStatus(
|
||||
// currentProgress: 100,
|
||||
// filename: documentFileName,
|
||||
// maxProgress: 100,
|
||||
// message: ProcessingMessage.finished,
|
||||
// status: ProcessingStatus.success,
|
||||
// taskId: DocumentProcessingStatus.unknownTaskId,
|
||||
// documentId: docId,
|
||||
// isApproximated: true,
|
||||
// ),
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// sleep(const Duration(seconds: 1));
|
||||
// } 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/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
abstract class LocalVault {
|
||||
Future<void> storeAuthenticationInformation(AuthenticationInformation auth);
|
||||
@@ -17,8 +16,6 @@ abstract class LocalVault {
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
@prod
|
||||
@Injectable(as: LocalVault)
|
||||
class LocalVaultImpl implements LocalVault {
|
||||
static const applicationSettingsKey = "applicationSettings";
|
||||
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:introduction_screen/introduction_screen.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/view/widgets/biometric_authentication_setting.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
||||
@@ -26,84 +25,81 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: BlocProvider.value(
|
||||
value: getIt<ApplicationSettingsCubit>(),
|
||||
child: IntroductionScreen(
|
||||
globalBackgroundColor: Theme.of(context).canvasColor,
|
||||
showDoneButton: true,
|
||||
next: Text(S.of(context).onboardingNextButtonLabel),
|
||||
done: Text(S.of(context).onboardingDoneButtonLabel),
|
||||
onDone: () => Navigator.pop(context),
|
||||
dotsDecorator: DotsDecorator(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
activeSize: const Size(16.0, 8.0),
|
||||
activeShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
||||
child: IntroductionScreen(
|
||||
globalBackgroundColor: Theme.of(context).canvasColor,
|
||||
showDoneButton: true,
|
||||
next: Text(S.of(context).onboardingNextButtonLabel),
|
||||
done: Text(S.of(context).onboardingDoneButtonLabel),
|
||||
onDone: () => Navigator.pop(context),
|
||||
dotsDecorator: DotsDecorator(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
activeSize: const Size(16.0, 8.0),
|
||||
activeShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
||||
),
|
||||
),
|
||||
pages: [
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"Always right at your fingertip",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(
|
||||
image: AssetImages.organizeDocuments.image,
|
||||
),
|
||||
),
|
||||
bodyWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(
|
||||
"Organizing documents was never this easy",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pages: [
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"Always right at your fingertip",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(
|
||||
image: AssetImages.organizeDocuments.image,
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"Accessible only by you",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(image: AssetImages.secureDocuments.image),
|
||||
),
|
||||
bodyWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(
|
||||
"Secure your documents with biometric authentication and client certificates",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
bodyWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(
|
||||
"Organizing documents was never this easy",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"Accessible only by you",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(image: AssetImages.secureDocuments.image),
|
||||
),
|
||||
bodyWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(
|
||||
"Secure your documents with biometric authentication and client certificates",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"You're almost done",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
PageViewModel(
|
||||
titleWidget: Text(
|
||||
"You're almost done",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(image: AssetImages.success.image),
|
||||
),
|
||||
bodyWidget: Column(
|
||||
children: const [
|
||||
BiometricAuthenticationSetting(),
|
||||
LanguageSelectionSetting(),
|
||||
ThemeModeSetting(),
|
||||
],
|
||||
),
|
||||
image: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image(image: AssetImages.success.image),
|
||||
),
|
||||
],
|
||||
),
|
||||
bodyWidget: Column(
|
||||
children: const [
|
||||
BiometricAuthenticationSetting(),
|
||||
LanguageSelectionSetting(),
|
||||
ThemeModeSetting(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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_mobile/core/repository/label_repository.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/features/document_details/bloc/document_details_cubit.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/util.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class DocumentDetailsPage extends StatefulWidget {
|
||||
@@ -50,7 +50,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.of(context)
|
||||
.pop(BlocProvider.of<DocumentDetailsCubit>(context).state.document);
|
||||
.pop(context.read<DocumentDetailsCubit>().state.document);
|
||||
return false;
|
||||
},
|
||||
child: DefaultTabController(
|
||||
@@ -106,9 +106,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
.black, //TODO: check if there is a way to dynamically determine color...
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(
|
||||
BlocProvider.of<DocumentDetailsCubit>(context)
|
||||
.state
|
||||
.document,
|
||||
context.read<DocumentDetailsCubit>().state.document,
|
||||
),
|
||||
),
|
||||
floating: true,
|
||||
@@ -185,29 +183,18 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Future<void> _onEdit(DocumentModel document) async {
|
||||
{
|
||||
final cubit = BlocProvider.of<DocumentDetailsCubit>(context);
|
||||
final cubit = context.read<DocumentDetailsCubit>();
|
||||
Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => EditDocumentCubit(
|
||||
document,
|
||||
documentsApi: getIt<PaperlessDocumentsApi>(),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
documentTypeRepository:
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
storagePathRepository:
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(
|
||||
context,
|
||||
),
|
||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
documentsApi: context.watch(),
|
||||
correspondentRepository: context.watch(),
|
||||
documentTypeRepository: context.watch(),
|
||||
storagePathRepository: context.watch(),
|
||||
tagRepository: context.watch(),
|
||||
),
|
||||
child: BlocListener<EditDocumentCubit, EditDocumentState>(
|
||||
listenWhen: (previous, current) =>
|
||||
@@ -226,7 +213,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
||||
return FutureBuilder<DocumentMetaData>(
|
||||
future: getIt<PaperlessDocumentsApi>().getMetaData(document),
|
||||
future: context.read<PaperlessDocumentsApi>().getMetaData(document),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@@ -281,7 +268,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Future<void> _assignAsn(DocumentModel document) async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentDetailsCubit>(context).assignAsn(document);
|
||||
await context.read<DocumentDetailsCubit>().assignAsn(document);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
@@ -393,7 +380,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
///
|
||||
Future<void> _onShare(DocumentModel document) async {
|
||||
Uint8List documentBytes =
|
||||
await getIt<PaperlessDocumentsApi>().download(document);
|
||||
await context.read<PaperlessDocumentsApi>().download(document);
|
||||
final dir = await getTemporaryDirectory();
|
||||
final String path = "${dir.path}/${document.originalFileName}";
|
||||
await File(path).writeAsBytes(documentBytes);
|
||||
@@ -419,7 +406,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
false;
|
||||
if (delete) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentDetailsCubit>(context).delete(document);
|
||||
await context.read<DocumentDetailsCubit>().delete(document);
|
||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
@@ -434,7 +421,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
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:paperless_api/paperless_api.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/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DocumentDownloadButton extends StatefulWidget {
|
||||
final DocumentModel? document;
|
||||
@@ -43,7 +43,8 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
||||
}
|
||||
setState(() => _isDownloadPending = true);
|
||||
try {
|
||||
final bytes = await getIt<PaperlessDocumentsApi>().download(document);
|
||||
final bytes =
|
||||
await context.read<PaperlessDocumentsApi>().download(document);
|
||||
final Directory dir = await FileService.downloadsDirectory;
|
||||
String filePath = "${dir.path}/${document.originalFileName}";
|
||||
//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,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
authToken: auth.token!,
|
||||
serverUrl: auth.serverUrl,
|
||||
);
|
||||
if (onConsumptionFinished != null) {
|
||||
_documentApi
|
||||
|
||||
@@ -166,10 +166,8 @@ class _DocumentUploadPreparationPageState
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
RepositoryProvider<LabelRepository<DocumentType>>(
|
||||
create: (context) => context.watch(),
|
||||
child: AddDocumentTypePage(initialName: initialName),
|
||||
),
|
||||
textFieldLabel:
|
||||
@@ -182,11 +180,8 @@ class _DocumentUploadPreparationPageState
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
RepositoryProvider<LabelRepository<Correspondent>>(
|
||||
create: (context) => context.watch(),
|
||||
child: AddCorrespondentPage(initialName: initialName),
|
||||
),
|
||||
textFieldLabel:
|
||||
@@ -220,7 +215,7 @@ class _DocumentUploadPreparationPageState
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final cubit = BlocProvider.of<DocumentUploadCubit>(context);
|
||||
final cubit = context.read<DocumentUploadCubit>();
|
||||
try {
|
||||
setState(() => _isUploadLoading = true);
|
||||
|
||||
|
||||
@@ -99,8 +99,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<StoragePath>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider<LabelRepository<StoragePath>>(
|
||||
create: (context) => context.watch(),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
||||
@@ -116,10 +117,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<Correspondent>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider<LabelRepository<Correspondent>>(
|
||||
create: context.watch(),
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
||||
@@ -135,10 +135,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
return LabelFormField<DocumentType>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
RepositoryProvider<LabelRepository<DocumentType>>(
|
||||
create: (context) => context.watch(),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
@@ -170,8 +169,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
_isSubmitLoading = true;
|
||||
});
|
||||
try {
|
||||
await BlocProvider.of<EditDocumentCubit>(context)
|
||||
.updateDocument(mergedDocument);
|
||||
await context.read<EditDocumentCubit>().updateDocument(mergedDocument);
|
||||
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||
} on PaperlessServerException catch (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_mobile/core/bloc/connectivity_cubit.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/view/pages/document_details_page.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/view_type.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DocumentsPage extends StatefulWidget {
|
||||
const DocumentsPage({Key? key}) : super(key: key);
|
||||
@@ -42,8 +42,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
_savedViewCubit = BlocProvider.of<SavedViewCubit>(context);
|
||||
_documentsCubit = context.watch();
|
||||
_savedViewCubit = context.watch();
|
||||
try {
|
||||
_documentsCubit.load();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
@@ -249,8 +249,11 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
||||
DocumentModel document) {
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: DocumentDetailsCubit(getIt<PaperlessDocumentsApi>(), document),
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.watch(),
|
||||
document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.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';
|
||||
|
||||
class DocumentPreview extends StatelessWidget {
|
||||
@@ -30,14 +30,15 @@ class DocumentPreview extends StatelessWidget {
|
||||
fit: fit,
|
||||
alignment: Alignment.topCenter,
|
||||
cacheKey: "thumb_$id",
|
||||
imageUrl: getIt<PaperlessDocumentsApi>().getThumbnailUrl(id),
|
||||
imageUrl:
|
||||
Provider.of<PaperlessDocumentsApi>(context).getThumbnailUrl(id),
|
||||
errorWidget: (ctxt, msg, __) => Text(msg),
|
||||
placeholder: (context, value) => Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[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),
|
||||
),
|
||||
),
|
||||
builder: (_) => BlocProvider.value(
|
||||
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
child: FractionallySizedBox(
|
||||
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/repository/label_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/view/pages/documents_page.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/view/scanner_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
@@ -56,7 +56,8 @@ class _HomePageState extends State<HomePage> {
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||
value:
|
||||
DocumentsCubit(Provider.of<PaperlessDocumentsApi>(context)),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
@@ -70,8 +71,10 @@ class _HomePageState extends State<HomePage> {
|
||||
value: _scannerCubit,
|
||||
child: const ScannerPage(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||
BlocProvider(
|
||||
create: (context) => DocumentsCubit(
|
||||
Provider.of<PaperlessDocumentsApi>(context),
|
||||
),
|
||||
child: const LabelsPage(),
|
||||
),
|
||||
][_currentIndex],
|
||||
@@ -81,13 +84,12 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
void _initializeData(BuildContext context) {
|
||||
try {
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context).findAll();
|
||||
RepositoryProvider.of<SavedViewRepository>(context).findAll();
|
||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
||||
.updateInformtion();
|
||||
context.read<LabelRepository<Tag>>().findAll();
|
||||
context.read<LabelRepository<Correspondent>>().findAll();
|
||||
context.read<LabelRepository<DocumentType>>().findAll();
|
||||
context.read<LabelRepository<StoragePath>>().findAll();
|
||||
context.read<SavedViewRepository>().findAll();
|
||||
context.read<PaperlessServerInformationCubit>().updateInformtion();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.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_mobile/core/bloc/paperless_server_information_cubit.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/saved_view_repository.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/features/inbox/bloc/inbox_cubit.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/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class InfoDrawer extends StatelessWidget {
|
||||
class InfoDrawer extends StatefulWidget {
|
||||
final VoidCallback? afterInboxClosed;
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
@@ -140,8 +155,9 @@ class InfoDrawer extends StatelessWidget {
|
||||
),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<ApplicationSettingsCubit>(),
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) =>
|
||||
Provider.of<ApplicationSettingsCubit>(context),
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
@@ -155,44 +171,51 @@ class InfoDrawer extends StatelessWidget {
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||
},
|
||||
),
|
||||
AboutListTile(
|
||||
icon: const Icon(Icons.info),
|
||||
applicationIcon: const ImageIcon(
|
||||
AssetImage('assets/logos/paperless_logo_green.png')),
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion:
|
||||
kPackageInfo.version + '+' + kPackageInfo.buildNumber,
|
||||
aboutBoxChildren: [
|
||||
Text(
|
||||
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
||||
Link(
|
||||
uri: Uri.parse(
|
||||
'https://github.com/astubenbord/paperless-mobile'),
|
||||
builder: (context, followLink) => GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'https://github.com/astubenbord/paperless-mobile',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.tertiary),
|
||||
FutureBuilder<PackageInfo>(
|
||||
future: _packageInfo,
|
||||
builder: (context, snapshot) {
|
||||
return AboutListTile(
|
||||
icon: const Icon(Icons.info),
|
||||
applicationIcon: const ImageIcon(
|
||||
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Credits',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
_buildOnboardingImageCredits(),
|
||||
],
|
||||
child: Text(S.of(context).appDrawerAboutLabel),
|
||||
),
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion: (snapshot.data?.version ?? '') +
|
||||
'+' +
|
||||
(snapshot.data?.buildNumber ?? ''),
|
||||
aboutBoxChildren: [
|
||||
Text(
|
||||
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
||||
Link(
|
||||
uri: Uri.parse(
|
||||
'https://github.com/astubenbord/paperless-mobile'),
|
||||
builder: (context, followLink) => GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'https://github.com/astubenbord/paperless-mobile',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Credits',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
_buildOnboardingImageCredits(),
|
||||
],
|
||||
child: Text(S.of(context).appDrawerAboutLabel),
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||
onTap: () {
|
||||
try {
|
||||
BlocProvider.of<AuthenticationCubit>(context).logout();
|
||||
getIt<LocalVault>().clear();
|
||||
Provider.of<LocalVault>(context).clear();
|
||||
BlocProvider.of<ApplicationSettingsCubit>(context).clear();
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context)
|
||||
.clear();
|
||||
@@ -224,7 +247,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
child: BlocProvider(
|
||||
create: (context) => InboxCubit(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
Provider.of<PaperlessDocumentsApi>(context),
|
||||
)..loadInbox(),
|
||||
child: const InboxPage(),
|
||||
),
|
||||
@@ -232,7 +255,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
maintainState: false,
|
||||
),
|
||||
);
|
||||
afterInboxClosed?.call();
|
||||
widget.afterInboxClosed?.call();
|
||||
}
|
||||
|
||||
Link _buildOnboardingImageCredits() {
|
||||
|
||||
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.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/view/pages/document_details_page.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:provider/provider.dart';
|
||||
|
||||
class InboxItem extends StatelessWidget {
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
@@ -48,9 +48,9 @@ class InboxItem extends StatelessWidget {
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: DocumentDetailsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
Provider.of<PaperlessDocumentsApi>(context),
|
||||
document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/view/pages/linked_documents_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LabelItem<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
@@ -45,9 +45,9 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: LinkedDocumentsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => LinkedDocumentsCubit(
|
||||
Provider.of<PaperlessDocumentsApi>(context),
|
||||
filter,
|
||||
),
|
||||
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:paperless_api/paperless_api.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/view/pages/document_details_page.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/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LinkedDocumentsPage extends StatefulWidget {
|
||||
const LinkedDocumentsPage({super.key});
|
||||
@@ -63,10 +63,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
BlocProvider<DocumentDetailsCubit>.value(
|
||||
value: DocumentDetailsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
Provider.of<PaperlessDocumentsApi>(
|
||||
context),
|
||||
document,
|
||||
),
|
||||
child: const DocumentDetailsPage(
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.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/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/client_certificate.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/settings/model/application_settings_state.dart';
|
||||
|
||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
class AuthenticationCubit extends HydratedCubit<AuthenticationState> {
|
||||
final LocalAuthenticationService _localAuthService;
|
||||
PaperlessAuthenticationApi _authApi;
|
||||
final PaperlessAuthenticationApi _authApi;
|
||||
final LocalVault _localVault;
|
||||
final SecurityContextAwareDioManager _dioWrapper;
|
||||
|
||||
AuthenticationCubit(
|
||||
this._localVault,
|
||||
this._localAuthService,
|
||||
this._authApi,
|
||||
this._dioWrapper,
|
||||
) : super(AuthenticationState.initial);
|
||||
|
||||
Future<void> login({
|
||||
@@ -28,22 +32,21 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
}) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
try {
|
||||
await registerSecurityContext(clientCertificate);
|
||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
||||
// Store information required to make requests
|
||||
final currentAuth = AuthenticationInformation(
|
||||
serverUrl: serverUrl,
|
||||
_dioWrapper.updateSettings(
|
||||
baseUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
await _localVault.storeAuthenticationInformation(currentAuth);
|
||||
|
||||
final token = await _authApi.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
);
|
||||
|
||||
final auth = currentAuth.copyWith(token: token);
|
||||
final auth = AuthenticationInformation(
|
||||
serverUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
token: token,
|
||||
);
|
||||
|
||||
await _localVault.storeAuthenticationInformation(auth);
|
||||
|
||||
@@ -83,9 +86,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final localAuthSuccess = await _localAuthService
|
||||
.authenticateLocalUser("Authenticate to log back in");
|
||||
if (localAuthSuccess) {
|
||||
await registerSecurityContext(storedAuth.clientCertificate);
|
||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
||||
_dioWrapper.updateSettings(
|
||||
clientCertificate: storedAuth.clientCertificate,
|
||||
);
|
||||
return emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
@@ -102,7 +105,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
));
|
||||
}
|
||||
} else {
|
||||
await registerSecurityContext(storedAuth.clientCertificate);
|
||||
_dioWrapper.updateSettings(
|
||||
clientCertificate: storedAuth.clientCertificate,
|
||||
);
|
||||
final authState = AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
authentication: storedAuth,
|
||||
@@ -115,40 +120,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
|
||||
Future<void> logout() async {
|
||||
await _localVault.clear();
|
||||
await super.clear();
|
||||
emit(AuthenticationState.initial);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AuthenticationState? fromJson(Map<String, dynamic> json) =>
|
||||
AuthenticationState.fromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(AuthenticationState state) => state.toJson();
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
class AuthenticationInformation {
|
||||
static const tokenKey = 'token';
|
||||
static const serverUrlKey = 'serverUrl';
|
||||
static const clientCertificateKey = 'clientCertificate';
|
||||
part 'authentication_information.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AuthenticationInformation {
|
||||
final String? token;
|
||||
final String serverUrl;
|
||||
final ClientCertificate? clientCertificate;
|
||||
@@ -16,21 +15,6 @@ class AuthenticationInformation {
|
||||
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 {
|
||||
return serverUrl.isNotEmpty && (token?.isNotEmpty ?? false);
|
||||
}
|
||||
@@ -48,4 +32,9 @@ class AuthenticationInformation {
|
||||
(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_form_builder/flutter_form_builder.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:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ServerAddressFormField extends StatefulWidget {
|
||||
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
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
||||
final isReachable =
|
||||
await getIt<ConnectivityStatusService>().isServerReachable(address);
|
||||
await Provider.of<ConnectivityStatusService>(context, listen: false)
|
||||
.isServerReachable(address);
|
||||
if (isReachable) {
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.reachable);
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.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';
|
||||
|
||||
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/store/local_vault.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/view/document_upload_preparation_page.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/widgets.dart' as pw;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ScannerPage extends StatefulWidget {
|
||||
const ScannerPage({Key? key}) : super(key: key);
|
||||
@@ -139,8 +139,8 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: getIt<LocalVault>(),
|
||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||
localVault: Provider.of<LocalVault>(context),
|
||||
documentApi: Provider.of<PaperlessDocumentsApi>(context),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
@@ -274,8 +274,8 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: getIt<LocalVault>(),
|
||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||
localVault: Provider.of<LocalVault>(context),
|
||||
documentApi: Provider.of<PaperlessDocumentsApi>(context),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.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';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@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);
|
||||
}
|
||||
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
||||
ApplicationSettingsCubit() : super(ApplicationSettingsState.defaultSettings);
|
||||
|
||||
Future<void> setLocale(String? localeSubtag) async {
|
||||
final updatedSettings = state.copyWith(preferredLocaleSubtag: localeSubtag);
|
||||
@@ -42,11 +28,20 @@ class ApplicationSettingsCubit extends Cubit<ApplicationSettingsState> {
|
||||
}
|
||||
|
||||
Future<void> _updateSettings(ApplicationSettingsState settings) async {
|
||||
await localVault.storeApplicationSettings(settings);
|
||||
emit(settings);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await super.clear();
|
||||
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 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/core/type/types.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.
|
||||
///
|
||||
///
|
||||
@JsonSerializable()
|
||||
class ApplicationSettingsState {
|
||||
static final defaultSettings = ApplicationSettingsState(
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredLocaleSubtag: Platform.localeName.split('_').first,
|
||||
preferredThemeMode: ThemeMode.system,
|
||||
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 String preferredLocaleSubtag;
|
||||
final ThemeMode preferredThemeMode;
|
||||
final ViewType preferredViewType;
|
||||
final bool showInboxOnStartup;
|
||||
|
||||
ApplicationSettingsState({
|
||||
required this.preferredLocaleSubtag,
|
||||
required this.preferredThemeMode,
|
||||
required this.isLocalAuthenticationEnabled,
|
||||
required this.preferredViewType,
|
||||
required this.showInboxOnStartup,
|
||||
});
|
||||
|
||||
JSON toJson() {
|
||||
return {
|
||||
isLocalAuthenticationEnabledKey: isLocalAuthenticationEnabled,
|
||||
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;
|
||||
Map<String, dynamic> toJson() => _$ApplicationSettingsStateToJson(this);
|
||||
factory ApplicationSettingsState.fromJson(Map<String, dynamic> json) =>
|
||||
_$ApplicationSettingsStateFromJson(json);
|
||||
|
||||
ApplicationSettingsState copyWith({
|
||||
bool? isLocalAuthenticationEnabled,
|
||||
String? preferredLocaleSubtag,
|
||||
ThemeMode? preferredThemeMode,
|
||||
ViewType? preferredViewType,
|
||||
bool? showInboxOnStartup,
|
||||
}) {
|
||||
return ApplicationSettingsState(
|
||||
isLocalAuthenticationEnabled:
|
||||
@@ -74,7 +48,6 @@ class ApplicationSettingsState {
|
||||
preferredLocaleSubtag ?? this.preferredLocaleSubtag,
|
||||
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
|
||||
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_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/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BiometricAuthenticationSetting extends StatelessWidget {
|
||||
const BiometricAuthenticationSetting({super.key});
|
||||
@@ -28,8 +28,9 @@ class BiometricAuthenticationSetting extends StatelessWidget {
|
||||
: S
|
||||
.of(context)
|
||||
.appSettingsDisableBiometricAuthenticationReasonText;
|
||||
final changeValue = await getIt<LocalAuthenticationService>()
|
||||
.authenticateLocalUser(localizedReason);
|
||||
final changeValue =
|
||||
await Provider.of<LocalAuthenticationService>(context)
|
||||
.authenticateLocalUser(localizedReason);
|
||||
if (changeValue) {
|
||||
settingsBloc.setIsBiometricAuthenticationEnabled(val);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
||||
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 {
|
||||
const ClearStorageSetting({super.key});
|
||||
@@ -12,12 +12,10 @@ class ClearStorageSetting extends StatelessWidget {
|
||||
title: Text("Clear data"),
|
||||
subtitle:
|
||||
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 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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_native_splash/flutter_native_splash.dart';
|
||||
import 'package:fluttertoast/fluttertoast.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/intl.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:paperless_api/paperless_api.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/paperless_server_information_cubit.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/model/paperless_statistics_state.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/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/label_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/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.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/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/home/view/home_page.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/view/login_page.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/generated/l10n.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';
|
||||
|
||||
void main() async {
|
||||
Bloc.observer = BlocChangesObserver();
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
HydratedBloc.storage = await HydratedStorage.build(
|
||||
storageDirectory: await getApplicationDocumentsDirectory(),
|
||||
);
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
Intl.systemLocale = await findSystemLocale();
|
||||
await findSystemLocale();
|
||||
|
||||
// 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.
|
||||
|
||||
(await FileService.temporaryDirectory).deleteSync(recursive: true);
|
||||
kPackageInfo = await PackageInfo.fromPlatform();
|
||||
// Load application settings and stored authentication data
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await connectivityCubit.initialize();
|
||||
|
||||
final authCubit = AuthenticationCubit(
|
||||
getIt<LocalVault>(),
|
||||
getIt<LocalAuthenticationService>(),
|
||||
getIt<PaperlessAuthenticationApi>(),
|
||||
localVault,
|
||||
localAuthService,
|
||||
authApi,
|
||||
dioWrapper,
|
||||
);
|
||||
await authCubit.restoreSessionState();
|
||||
//TODO: Check if hydrated cubit restores state.
|
||||
//await authCubit.restoreSessionState();
|
||||
|
||||
// Create repositories
|
||||
final LabelRepository<Tag> tagRepository =
|
||||
TagRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<Correspondent> correspondentRepository =
|
||||
CorrespondentRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<DocumentType> documentTypeRepository =
|
||||
DocumentTypeRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<StoragePath> storagePathRepository =
|
||||
StoragePathRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final SavedViewRepository savedViewRepository =
|
||||
SavedViewRepositoryImpl(getIt<PaperlessSavedViewsApi>());
|
||||
final tagRepository = TagRepositoryImpl(labelsApi);
|
||||
final correspondentRepository = CorrespondentRepositoryImpl(labelsApi);
|
||||
final documentTypeRepository = DocumentTypeRepositoryImpl(labelsApi);
|
||||
final storagePathRepository = StoragePathRepositoryImpl(labelsApi);
|
||||
final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi);
|
||||
|
||||
runApp(
|
||||
MultiRepositoryProvider(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(value: tagRepository),
|
||||
RepositoryProvider.value(value: correspondentRepository),
|
||||
RepositoryProvider.value(value: documentTypeRepository),
|
||||
RepositoryProvider.value(value: storagePathRepository),
|
||||
RepositoryProvider.value(value: savedViewRepository),
|
||||
Provider<PaperlessAuthenticationApi>.value(value: authApi),
|
||||
Provider<PaperlessDocumentsApi>.value(value: documentsApi),
|
||||
Provider<PaperlessLabelsApi>.value(value: labelsApi),
|
||||
Provider<PaperlessServerStatsApi>.value(value: statsApi),
|
||||
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 {
|
||||
final AuthenticationCubit authenticationCubit;
|
||||
const PaperlessMobileEntrypoint({
|
||||
Key? key,
|
||||
required this.authenticationCubit,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -153,14 +224,14 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ConnectivityCubit>.value(
|
||||
value: getIt<ConnectivityCubit>(),
|
||||
BlocProvider(
|
||||
create: (context) => ConnectivityCubit(context.watch()),
|
||||
),
|
||||
BlocProvider<PaperlessServerInformationCubit>.value(
|
||||
value: getIt<PaperlessServerInformationCubit>(),
|
||||
BlocProvider(
|
||||
create: (context) => PaperlessServerInformationCubit(context.watch()),
|
||||
),
|
||||
BlocProvider<ApplicationSettingsCubit>.value(
|
||||
value: getIt<ApplicationSettingsCubit>(),
|
||||
BlocProvider(
|
||||
create: (context) => ApplicationSettingsCubit(),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
@@ -188,10 +259,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
home: BlocProvider.value(
|
||||
value: widget.authenticationCubit,
|
||||
child: const AuthenticationWrapper(),
|
||||
),
|
||||
home: const AuthenticationWrapper(),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -247,19 +315,11 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider(
|
||||
create: (BuildContext context) => DocumentUploadCubit(
|
||||
localVault: getIt<LocalVault>(),
|
||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
documentTypeRepository:
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
localVault: context.watch(),
|
||||
documentApi: context.watch(),
|
||||
tagRepository: context.watch(),
|
||||
correspondentRepository: context.watch(),
|
||||
documentTypeRepository: context.watch(),
|
||||
),
|
||||
child: DocumentUploadPreparationPage(
|
||||
fileBytes: bytes,
|
||||
@@ -349,13 +409,12 @@ class BiometricAuthenticationPage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
BlocProvider.of<AuthenticationCubit>(context).logout(),
|
||||
onPressed: () => context.read<AuthenticationCubit>().logout(),
|
||||
child: const Text("Log out"),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => BlocProvider.of<AuthenticationCubit>(context)
|
||||
.restoreSessionState(),
|
||||
onPressed: () =>
|
||||
context.read<AuthenticationCubit>().restoreSessionState(),
|
||||
child: const Text("Authenticate"),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
final dateFormat = DateFormat("yyyy-MM-dd");
|
||||
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||
late PackageInfo kPackageInfo;
|
||||
|
||||
class SnackBarActionConfig {
|
||||
final String label;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import 'dart:convert';
|
||||
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/modules/authentication_api/authentication_api.dart';
|
||||
|
||||
class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||
final BaseClient client;
|
||||
final Dio client;
|
||||
|
||||
PaperlessAuthenticationApiImpl(this.client);
|
||||
|
||||
@@ -18,8 +15,8 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||
late Response response;
|
||||
try {
|
||||
response = await client.post(
|
||||
Uri.parse("/api/token/"),
|
||||
body: {
|
||||
"/api/token/",
|
||||
data: {
|
||||
"username": username,
|
||||
"password": password,
|
||||
},
|
||||
@@ -34,11 +31,11 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
||||
return data['token'];
|
||||
} else if (response.statusCode == HttpStatus.badRequest &&
|
||||
response.body
|
||||
if (response.statusCode == 200) {
|
||||
return response.data['token'];
|
||||
} else if (response.statusCode == 400 &&
|
||||
response
|
||||
.data //TODO: Check if text is included in statusMessage instead of body
|
||||
.toLowerCase()
|
||||
.contains("no required certificate was sent")) {
|
||||
throw PaperlessServerException(
|
||||
|
||||
@@ -12,12 +12,10 @@ abstract class PaperlessDocumentsApi {
|
||||
Uint8List documentBytes, {
|
||||
required String filename,
|
||||
required String title,
|
||||
required String authToken,
|
||||
required String serverUrl,
|
||||
DateTime? createdAt,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
});
|
||||
Future<DocumentModel> update(DocumentModel doc);
|
||||
Future<int> findNextAsn();
|
||||
|
||||
@@ -1,94 +1,42 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:http/src/boundary_characters.dart';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:paperless_api/paperless_api.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 {
|
||||
final BaseClient baseClient;
|
||||
final HttpClient httpClient;
|
||||
final Dio client;
|
||||
|
||||
PaperlessDocumentsApiImpl(this.baseClient, this.httpClient);
|
||||
PaperlessDocumentsApiImpl(this.client);
|
||||
|
||||
@override
|
||||
Future<void> create(
|
||||
Uint8List documentBytes, {
|
||||
required String filename,
|
||||
required String title,
|
||||
required String authToken,
|
||||
required String serverUrl,
|
||||
DateTime? createdAt,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
}) async {
|
||||
// The multipart request has to be generated from scratch as the http library does
|
||||
// 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 formData = FormData();
|
||||
|
||||
final boundary = _boundaryString();
|
||||
|
||||
StringBuffer bodyBuffer = StringBuffer();
|
||||
|
||||
var fields = <String, String>{};
|
||||
fields.putIfAbsent('title', () => title);
|
||||
formData.fields.add(MapEntry('title', title));
|
||||
if (createdAt != null) {
|
||||
fields.putIfAbsent('created', () => apiDateFormat.format(createdAt));
|
||||
formData.fields.add(MapEntry('created', apiDateFormat.format(createdAt)));
|
||||
}
|
||||
if (correspondent != null) {
|
||||
fields.putIfAbsent('correspondent', () => jsonEncode(correspondent));
|
||||
formData.fields.add(MapEntry('correspondent', jsonEncode(correspondent)));
|
||||
}
|
||||
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) {
|
||||
bodyBuffer.write(_buildMultipartField('tags', tag.toString(), boundary));
|
||||
formData.fields.add(MapEntry('tags', tag.toString()));
|
||||
}
|
||||
|
||||
bodyBuffer.write("--$boundary"
|
||||
'\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();
|
||||
|
||||
final response =
|
||||
await client.post('/api/documents/post_document/', data: formData);
|
||||
if (response.statusCode != 200) {
|
||||
throw PaperlessServerException(
|
||||
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
|
||||
Future<DocumentModel> update(DocumentModel doc) async {
|
||||
final response = await baseClient.put(
|
||||
Uri.parse("/api/documents/${doc.id}/"),
|
||||
body: json.encode(doc.toJson()),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
final response = await client.put(
|
||||
"/api/documents/${doc.id}/",
|
||||
data: doc.toJson(),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return DocumentModel.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
);
|
||||
return DocumentModel.fromJson(response.data);
|
||||
} else {
|
||||
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
||||
}
|
||||
@@ -137,17 +61,15 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
@override
|
||||
Future<PagedSearchResult<DocumentModel>> find(DocumentFilter filter) async {
|
||||
final filterParams = filter.toQueryParameters();
|
||||
final response = await baseClient.get(
|
||||
Uri(
|
||||
path: "/api/documents/",
|
||||
queryParameters: filterParams,
|
||||
),
|
||||
final response = await client.get(
|
||||
"/api/documents/",
|
||||
queryParameters: filterParams,
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return compute(
|
||||
PagedSearchResult.fromJson,
|
||||
PagedSearchResultJsonSerializer<DocumentModel>(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
response.data,
|
||||
DocumentModel.fromJson,
|
||||
),
|
||||
);
|
||||
@@ -158,8 +80,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
|
||||
@override
|
||||
Future<int> delete(DocumentModel doc) async {
|
||||
final response =
|
||||
await baseClient.delete(Uri.parse("/api/documents/${doc.id}/"));
|
||||
final response = await client.delete("/api/documents/${doc.id}/");
|
||||
|
||||
if (response.statusCode == 204) {
|
||||
return Future.value(doc.id);
|
||||
@@ -178,9 +99,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
|
||||
@override
|
||||
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) {
|
||||
return response.bodyBytes;
|
||||
return response.data;
|
||||
}
|
||||
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
||||
}
|
||||
@@ -207,10 +133,9 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
|
||||
@override
|
||||
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
||||
final response = await baseClient.post(
|
||||
Uri.parse("/api/documents/bulk_edit/"),
|
||||
body: json.encode(action.toJson()),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
final response = await client.post(
|
||||
"/api/documents/bulk_edit/",
|
||||
data: action.toJson(),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return action.documentIds;
|
||||
@@ -241,40 +166,48 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
|
||||
@override
|
||||
Future<Uint8List> download(DocumentModel document) async {
|
||||
final response = await baseClient
|
||||
.get(Uri.parse("/api/documents/${document.id}/download/"));
|
||||
return response.bodyBytes;
|
||||
//TODO: Add missing error handling
|
||||
final response = await client.get(
|
||||
"/api/documents/${document.id}/download/",
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
||||
final response = await baseClient
|
||||
.get(Uri.parse("/api/documents/${document.id}/metadata/"));
|
||||
final response =
|
||||
await client.get("/api/documents/${document.id}/metadata/");
|
||||
return compute(
|
||||
DocumentMetaData.fromJson,
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
response.data as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> autocomplete(String query, [int limit = 10]) async {
|
||||
final response = await baseClient
|
||||
.get(Uri.parse("/api/search/autocomplete/?query=$query&limit=$limit}"));
|
||||
final response = await client.get(
|
||||
'/api/search/autocomplete/',
|
||||
queryParameters: {
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(utf8.decode(response.bodyBytes)) as List<String>;
|
||||
return response.data as List<String>;
|
||||
}
|
||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SimilarDocumentModel>> findSimilar(int docId) async {
|
||||
final response = await baseClient
|
||||
.get(Uri.parse("/api/documents/?more_like=$docId&pageSize=10"));
|
||||
final response =
|
||||
await client.get("/api/documents/?more_like=$docId&pageSize=10");
|
||||
if (response.statusCode == 200) {
|
||||
return (await compute(
|
||||
PagedSearchResult<SimilarDocumentModel>.fromJson,
|
||||
PagedSearchResultJsonSerializer(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
response.data,
|
||||
SimilarDocumentModel.fromJson,
|
||||
),
|
||||
))
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import 'dart:convert';
|
||||
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/document_type_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/paperless_server_exception.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 {
|
||||
final BaseClient client;
|
||||
final Dio client;
|
||||
|
||||
PaperlessLabelApiImpl(this.client);
|
||||
@override
|
||||
@@ -89,15 +91,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<Correspondent> saveCorrespondent(Correspondent correspondent) async {
|
||||
final response = await client.post(
|
||||
Uri.parse('/api/correspondents/'),
|
||||
body: jsonEncode(correspondent.toJson()),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/correspondents/',
|
||||
data: correspondent.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return Correspondent.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
);
|
||||
return Correspondent.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.correspondentCreateFailed,
|
||||
@@ -108,15 +106,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<DocumentType> saveDocumentType(DocumentType type) async {
|
||||
final response = await client.post(
|
||||
Uri.parse('/api/document_types/'),
|
||||
body: json.encode(type.toJson()),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/document_types/',
|
||||
data: type.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return DocumentType.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
);
|
||||
return DocumentType.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.documentTypeCreateFailed,
|
||||
@@ -126,18 +120,13 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
|
||||
@override
|
||||
Future<Tag> saveTag(Tag tag) async {
|
||||
final body = json.encode(tag.toJson());
|
||||
final response = await client.post(
|
||||
Uri.parse('/api/tags/'),
|
||||
body: body,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json; version=2",
|
||||
},
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/tags/',
|
||||
data: tag.toJson(),
|
||||
options: Options(headers: {"Accept": "application/json; version=2"}),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return Tag.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return Tag.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.tagCreateFailed,
|
||||
@@ -148,8 +137,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
||||
assert(correspondent.id != null);
|
||||
final response = await client
|
||||
.delete(Uri.parse('/api/correspondents/${correspondent.id}/'));
|
||||
final response =
|
||||
await client.delete('/api/correspondents/${correspondent.id}/');
|
||||
if (response.statusCode == HttpStatus.noContent) {
|
||||
return correspondent.id!;
|
||||
}
|
||||
@@ -162,8 +151,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<int> deleteDocumentType(DocumentType documentType) async {
|
||||
assert(documentType.id != null);
|
||||
final response = await client
|
||||
.delete(Uri.parse('/api/document_types/${documentType.id}/'));
|
||||
final response =
|
||||
await client.delete('/api/document_types/${documentType.id}/');
|
||||
if (response.statusCode == HttpStatus.noContent) {
|
||||
return documentType.id!;
|
||||
}
|
||||
@@ -176,7 +165,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<int> deleteTag(Tag tag) async {
|
||||
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) {
|
||||
return tag.id!;
|
||||
}
|
||||
@@ -190,17 +179,14 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
||||
assert(correspondent.id != null);
|
||||
final response = await client.put(
|
||||
Uri.parse('/api/correspondents/${correspondent.id}/'),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(correspondent.toJson()),
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/correspondents/${correspondent.id}/',
|
||||
data: json.encode(correspondent.toJson()),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return Correspondent.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return Correspondent.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.unknown,
|
||||
ErrorCode.unknown, //TODO: Add correct error code mapping.
|
||||
httpStatusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
@@ -209,13 +195,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
||||
assert(documentType.id != null);
|
||||
final response = await client.put(
|
||||
Uri.parse('/api/document_types/${documentType.id}/'),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(documentType.toJson()),
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/document_types/${documentType.id}/',
|
||||
data: documentType.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return DocumentType.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return DocumentType.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.unknown,
|
||||
@@ -227,16 +211,12 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
Future<Tag> updateTag(Tag tag) async {
|
||||
assert(tag.id != null);
|
||||
final response = await client.put(
|
||||
Uri.parse('/api/tags/${tag.id}/'),
|
||||
headers: {
|
||||
"Accept": "application/json; version=2",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(tag.toJson()),
|
||||
encoding: Encoding.getByName("utf-8"),
|
||||
'/api/tags/${tag.id}/',
|
||||
options: Options(headers: {"Accept": "application/json; version=2"}),
|
||||
data: tag.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return Tag.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return Tag.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.unknown,
|
||||
@@ -247,8 +227,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<int> deleteStoragePath(StoragePath path) async {
|
||||
assert(path.id != null);
|
||||
final response =
|
||||
await client.delete(Uri.parse('/api/storage_paths/${path.id}/'));
|
||||
final response = await client.delete('/api/storage_paths/${path.id}/');
|
||||
if (response.statusCode == HttpStatus.noContent) {
|
||||
return path.id!;
|
||||
}
|
||||
@@ -285,12 +264,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<StoragePath> saveStoragePath(StoragePath path) async {
|
||||
final response = await client.post(
|
||||
Uri.parse('/api/storage_paths/'),
|
||||
body: json.encode(path.toJson()),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
'/api/storage_paths/',
|
||||
data: path.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return StoragePath.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return StoragePath.fromJson(jsonDecode(response.data));
|
||||
}
|
||||
throw PaperlessServerException(ErrorCode.storagePathCreateFailed,
|
||||
httpStatusCode: response.statusCode);
|
||||
@@ -300,12 +278,11 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
Future<StoragePath> updateStoragePath(StoragePath path) async {
|
||||
assert(path.id != null);
|
||||
final response = await client.put(
|
||||
Uri.parse('/api/storage_paths/${path.id}/'),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(path.toJson()),
|
||||
'/api/storage_paths/${path.id}/',
|
||||
data: path.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return StoragePath.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return StoragePath.fromJson(jsonDecode(response.data));
|
||||
}
|
||||
throw const PaperlessServerException(ErrorCode.unknown);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:convert';
|
||||
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/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';
|
||||
|
||||
class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||
final BaseClient client;
|
||||
final Dio client;
|
||||
|
||||
PaperlessSavedViewsApiImpl(this.client);
|
||||
|
||||
@@ -28,12 +28,11 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||
@override
|
||||
Future<SavedView> save(SavedView view) async {
|
||||
final response = await client.post(
|
||||
Uri.parse("/api/saved_views/"),
|
||||
body: jsonEncode(view.toJson()),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
"/api/saved_views/",
|
||||
data: view.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return SavedView.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return SavedView.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.createSavedViewError,
|
||||
@@ -43,8 +42,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||
|
||||
@override
|
||||
Future<int> delete(SavedView view) async {
|
||||
final response =
|
||||
await client.delete(Uri.parse("/api/saved_views/${view.id}/"));
|
||||
final response = await client.delete("/api/saved_views/${view.id}/");
|
||||
if (response.statusCode == HttpStatus.noContent) {
|
||||
return view.id!;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:paperless_api/src/models/paperless_server_exception.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.
|
||||
///
|
||||
class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
||||
final BaseClient client;
|
||||
final Dio client;
|
||||
|
||||
PaperlessServerStatsApiImpl(this.client);
|
||||
|
||||
@override
|
||||
Future<PaperlessServerInformationModel> getServerInformation() async {
|
||||
final response = await client.get(Uri.parse("/api/ui_settings/"));
|
||||
final version =
|
||||
response.headers[PaperlessServerInformationModel.versionHeader] ??
|
||||
'unknown';
|
||||
final apiVersion = int.tryParse(
|
||||
response.headers[PaperlessServerInformationModel.apiVersionHeader] ??
|
||||
'1');
|
||||
final String username =
|
||||
jsonDecode(utf8.decode(response.bodyBytes))['username'];
|
||||
final response = await client.get("/api/ui_settings/");
|
||||
final version = response
|
||||
.headers[PaperlessServerInformationModel.versionHeader]?.first ??
|
||||
'unknown';
|
||||
final apiVersion = int.tryParse(response
|
||||
.headers[PaperlessServerInformationModel.apiVersionHeader]?.first ??
|
||||
'1');
|
||||
final String username = response.data['username'];
|
||||
final String host = response
|
||||
.headers[PaperlessServerInformationModel.hostHeader] ??
|
||||
response.request?.headers[PaperlessServerInformationModel.hostHeader] ??
|
||||
('${response.request?.url.host}:${response.request?.url.port}');
|
||||
.headers[PaperlessServerInformationModel.hostHeader]?.first ??
|
||||
response.headers[PaperlessServerInformationModel.hostHeader]?.first ??
|
||||
('${response.requestOptions.uri.host}:${response.requestOptions.uri.port}');
|
||||
return PaperlessServerInformationModel(
|
||||
username: username,
|
||||
version: version,
|
||||
@@ -42,11 +42,9 @@ class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
||||
|
||||
@override
|
||||
Future<PaperlessServerStatisticsModel> getServerStatistics() async {
|
||||
final response = await client.get(Uri.parse('/api/statistics/'));
|
||||
final response = await client.get('/api/statistics/');
|
||||
if (response.statusCode == 200) {
|
||||
return PaperlessServerStatisticsModel.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
);
|
||||
return PaperlessServerStatisticsModel.fromJson(response.data);
|
||||
}
|
||||
throw const PaperlessServerException.unknown();
|
||||
}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||
|
||||
Future<T> getSingleResult<T>(
|
||||
String url,
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
ErrorCode errorCode, {
|
||||
required BaseClient client,
|
||||
required Dio client,
|
||||
int minRequiredApiVersion = 1,
|
||||
}) async {
|
||||
final response = await client.get(
|
||||
Uri.parse(url),
|
||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||
url,
|
||||
options: Options(
|
||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||
),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return compute(
|
||||
fromJson,
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
response.data as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
throw PaperlessServerException(
|
||||
@@ -32,16 +33,17 @@ Future<List<T>> getCollection<T>(
|
||||
String url,
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
ErrorCode errorCode, {
|
||||
required BaseClient client,
|
||||
required Dio client,
|
||||
int minRequiredApiVersion = 1,
|
||||
}) async {
|
||||
final response = await client.get(
|
||||
Uri.parse(url),
|
||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||
url,
|
||||
options: Options(headers: {
|
||||
'accept': 'application/json; version=$minRequiredApiVersion'
|
||||
}),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
final Map<String, dynamic> body =
|
||||
jsonDecode(utf8.decode(response.bodyBytes));
|
||||
final Map<String, dynamic> body = response.data;
|
||||
if (body.containsKey('count')) {
|
||||
if (body['count'] == 0) {
|
||||
return <T>[];
|
||||
@@ -17,7 +17,8 @@ dependencies:
|
||||
http: ^0.13.5
|
||||
json_annotation: ^4.7.0
|
||||
intl: ^0.17.0
|
||||
|
||||
dio: ^4.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
40
pubspec.lock
40
pubspec.lock
@@ -385,6 +385,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -665,7 +673,7 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
get_it:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: get_it
|
||||
sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7"
|
||||
@@ -736,6 +744,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -753,7 +769,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
injectable:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: injectable
|
||||
sha256: "7dab7d341feb40a0590d9ff6261aea9495522005e2c6763f9161a4face916f7b"
|
||||
@@ -814,13 +830,21 @@ packages:
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1221,7 +1245,7 @@ packages:
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
||||
@@ -1417,6 +1441,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -32,8 +32,6 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
get_it: ^7.2.0
|
||||
injectable: ^2.1.0
|
||||
encrypted_shared_preferences: ^3.0.0
|
||||
permission_handler: ^9.2.0
|
||||
pdf: ^3.8.1
|
||||
@@ -79,6 +77,11 @@ dependencies:
|
||||
rxdart: ^0.27.7
|
||||
badges: ^2.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:
|
||||
integration_test:
|
||||
@@ -92,6 +95,7 @@ dev_dependencies:
|
||||
dependency_validator: ^3.0.0
|
||||
intl_utils: ^2.7.0
|
||||
flutter_lints: ^1.0.0
|
||||
json_serializable: ^6.5.4
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
Reference in New Issue
Block a user