mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 22:07:53 -06:00
WIP - Replaced get_it + injectable with Provider
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user