WIP - Replaced get_it + injectable with Provider

This commit is contained in:
Anton Stubenbord
2022-12-21 01:14:06 +01:00
parent 10149fb7c1
commit 60aecb549d
59 changed files with 1099 additions and 1362 deletions

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 =>

View File

@@ -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);
}
}
}

View File

@@ -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,

View 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'});
}
}
}

View 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,
);
}
}

View File

View 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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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";

View File

@@ -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);
}

View File

@@ -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)));
}

View File

@@ -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);
}

View File

@@ -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(),
],
),
),
],
),
);
}

View File

@@ -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);
});
},
);
}),
],
),
],
);
},
);
}
}

View File

@@ -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),
),
),
);

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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(),
),

View File

@@ -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(),
),
// ),
);

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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(

View File

@@ -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(),

View File

@@ -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(

View File

@@ -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();
}

View 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);
}

View 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,
};

View File

@@ -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);
}

View 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,
};

View File

@@ -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 {

View File

@@ -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>> {

View 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,

View File

@@ -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();
}

View File

@@ -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,
);
}
}

View File

@@ -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',
};

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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"),
),
],

View File

@@ -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;