mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 01:15:44 -06:00
feat: Rework error handling, upgrade dio, fixed bugs
- Fix grey screen bug when adding labels from documnet upload - Add more permission checks to conditionally show widgets
This commit is contained in:
@@ -1,99 +1,46 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
|
|
||||||
class DioHttpErrorInterceptor extends Interceptor {
|
class DioHttpErrorInterceptor extends Interceptor {
|
||||||
@override
|
@override
|
||||||
void onError(DioError err, ErrorInterceptorHandler handler) {
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||||
if (err.response?.statusCode == 400) {
|
if (err.response?.statusCode == 400) {
|
||||||
// try to parse contained error message, otherwise return response
|
final data = err.response!.data;
|
||||||
final dynamic data = err.response?.data;
|
if (PaperlessServerMessageException.canParse(data)) {
|
||||||
if (data is Map<String, dynamic>) {
|
final exception = PaperlessServerMessageException.fromJson(data);
|
||||||
return _handlePaperlessValidationError(data, handler, err);
|
final message = exception.detail;
|
||||||
} else if (data is String) {
|
|
||||||
return _handlePlainError(data, handler, err);
|
|
||||||
}
|
|
||||||
} else if (err.response?.statusCode == 403) {
|
|
||||||
var data = err.response!.data;
|
|
||||||
if (data is Map && data.containsKey("detail")) {
|
|
||||||
handler.reject(
|
handler.reject(
|
||||||
DioError(
|
DioException(
|
||||||
message: data['detail'],
|
message: message,
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
error: ServerMessageException(data['detail']),
|
error: exception,
|
||||||
response: err.response,
|
response: err.response,
|
||||||
type: DioErrorType.unknown,
|
type: DioExceptionType.badResponse,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
} else if (PaperlessFormValidationException.canParse(data)) {
|
||||||
}
|
final exception = PaperlessFormValidationException.fromJson(data);
|
||||||
} else if (err.error is SocketException) {
|
handler.reject(
|
||||||
final ex = err.error as SocketException;
|
DioException(
|
||||||
if (ex.osError?.errorCode == _OsErrorCodes.serverUnreachable.code) {
|
|
||||||
return handler.reject(
|
|
||||||
DioError(
|
|
||||||
message: "The server could not be reached. Is the device offline?",
|
|
||||||
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.connectionTimeout,
|
error: exception,
|
||||||
|
response: err.response,
|
||||||
|
type: DioExceptionType.badResponse,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (data is String &&
|
||||||
|
data.contains("No required SSL certificate was sent")) {
|
||||||
|
handler.reject(
|
||||||
|
DioException(
|
||||||
|
requestOptions: err.requestOptions,
|
||||||
|
type: DioExceptionType.badResponse,
|
||||||
|
error:
|
||||||
|
const PaperlessApiException(ErrorCode.missingClientCertificate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
return handler.reject(err);
|
return handler.next(err);
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePaperlessValidationError(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
ErrorInterceptorHandler handler,
|
|
||||||
DioError err,
|
|
||||||
) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.reject(
|
|
||||||
DioError(
|
|
||||||
error: errorMessages,
|
|
||||||
requestOptions: err.requestOptions,
|
|
||||||
type: DioErrorType.badResponse,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePlainError(
|
|
||||||
String data,
|
|
||||||
ErrorInterceptorHandler handler,
|
|
||||||
DioError err,
|
|
||||||
) {
|
|
||||||
if (data.contains("No required SSL certificate was sent")) {
|
|
||||||
handler.reject(
|
|
||||||
DioError(
|
|
||||||
requestOptions: err.requestOptions,
|
|
||||||
type: DioErrorType.badResponse,
|
|
||||||
error: const PaperlessServerException(
|
|
||||||
ErrorCode.missingClientCertificate),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _OsErrorCodes {
|
|
||||||
serverUnreachable(101);
|
|
||||||
|
|
||||||
const _OsErrorCodes(this.code);
|
|
||||||
final int code;
|
|
||||||
}
|
|
||||||
|
|||||||
32
lib/core/interceptor/dio_offline_interceptor.dart
Normal file
32
lib/core/interceptor/dio_offline_interceptor.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
|
||||||
|
class DioOfflineInterceptor extends Interceptor {
|
||||||
|
@override
|
||||||
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||||
|
if (err.error is SocketException) {
|
||||||
|
final ex = err.error as SocketException;
|
||||||
|
if (ex.osError?.errorCode == _OsErrorCodes.serverUnreachable.code) {
|
||||||
|
handler.reject(
|
||||||
|
DioException(
|
||||||
|
message: "The host could not be reached. Is your device offline?",
|
||||||
|
error: const PaperlessApiException(ErrorCode.deviceOffline),
|
||||||
|
requestOptions: err.requestOptions,
|
||||||
|
type: DioExceptionType.connectionTimeout,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler.next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _OsErrorCodes {
|
||||||
|
serverUnreachable(101);
|
||||||
|
|
||||||
|
const _OsErrorCodes(this.code);
|
||||||
|
final int code;
|
||||||
|
}
|
||||||
27
lib/core/interceptor/dio_unauthorized_interceptor.dart
Normal file
27
lib/core/interceptor/dio_unauthorized_interceptor.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
|
||||||
|
class DioUnauthorizedInterceptor extends Interceptor {
|
||||||
|
@override
|
||||||
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||||
|
if (err.response?.statusCode == 403) {
|
||||||
|
final data = err.response!.data;
|
||||||
|
String? message;
|
||||||
|
if (PaperlessServerMessageException.canParse(data)) {
|
||||||
|
final exception = PaperlessServerMessageException.fromJson(data);
|
||||||
|
message = exception.detail;
|
||||||
|
}
|
||||||
|
handler.reject(
|
||||||
|
DioException(
|
||||||
|
message: message,
|
||||||
|
requestOptions: err.requestOptions,
|
||||||
|
error: PaperlessUnauthorizedException(message),
|
||||||
|
response: err.response,
|
||||||
|
type: DioExceptionType.badResponse,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handler.next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ class RetryOnConnectionChangeInterceptor extends Interceptor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onError(DioError err, ErrorInterceptorHandler handler) async {
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
if (_shouldRetryOnHttpException(err)) {
|
if (_shouldRetryOnHttpException(err)) {
|
||||||
try {
|
try {
|
||||||
handler.resolve(await DioHttpRequestRetrier(dio: dio)
|
handler.resolve(await DioHttpRequestRetrier(dio: dio)
|
||||||
@@ -27,8 +27,8 @@ class RetryOnConnectionChangeInterceptor extends Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRetryOnHttpException(DioError err) {
|
bool _shouldRetryOnHttpException(DioException err) {
|
||||||
return err.type == DioErrorType.unknown &&
|
return err.type == DioExceptionType.unknown &&
|
||||||
(err.error is HttpException &&
|
(err.error is HttpException &&
|
||||||
(err.message?.contains(
|
(err.message?.contains(
|
||||||
'Connection closed before full header was received',
|
'Connection closed before full header was received',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class ServerReachabilityErrorInterceptor extends Interceptor {
|
|||||||
static const _missingClientCertText = "No required SSL certificate was sent";
|
static const _missingClientCertText = "No required SSL certificate was sent";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onError(DioError err, ErrorInterceptorHandler handler) {
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||||
if (err.response?.statusCode == 400) {
|
if (err.response?.statusCode == 400) {
|
||||||
final message = err.response?.data;
|
final message = err.response?.data;
|
||||||
if (message is String && message.contains(_missingClientCertText)) {
|
if (message is String && message.contains(_missingClientCertText)) {
|
||||||
@@ -19,7 +19,7 @@ class ServerReachabilityErrorInterceptor extends Interceptor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err.type == DioErrorType.connectionTimeout) {
|
if (err.type == DioExceptionType.connectionTimeout) {
|
||||||
return _rejectWithStatus(
|
return _rejectWithStatus(
|
||||||
ReachabilityStatus.connectionTimeout,
|
ReachabilityStatus.connectionTimeout,
|
||||||
err,
|
err,
|
||||||
@@ -48,13 +48,13 @@ class ServerReachabilityErrorInterceptor extends Interceptor {
|
|||||||
|
|
||||||
void _rejectWithStatus(
|
void _rejectWithStatus(
|
||||||
ReachabilityStatus reachabilityStatus,
|
ReachabilityStatus reachabilityStatus,
|
||||||
DioError err,
|
DioException err,
|
||||||
ErrorInterceptorHandler handler,
|
ErrorInterceptorHandler handler,
|
||||||
) {
|
) {
|
||||||
handler.reject(DioError(
|
handler.reject(DioException(
|
||||||
error: reachabilityStatus,
|
error: reachabilityStatus,
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
response: err.response,
|
response: err.response,
|
||||||
type: DioErrorType.unknown,
|
type: DioExceptionType.unknown,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ Future<DocumentUploadResult?> pushDocumentUploadPreparationPage(
|
|||||||
final labelRepo = context.read<LabelRepository>();
|
final labelRepo = context.read<LabelRepository>();
|
||||||
final docsApi = context.read<PaperlessDocumentsApi>();
|
final docsApi = context.read<PaperlessDocumentsApi>();
|
||||||
final connectivity = context.read<Connectivity>();
|
final connectivity = context.read<Connectivity>();
|
||||||
|
final apiVersion = context.read<ApiVersion>();
|
||||||
return Navigator.of(context).push<DocumentUploadResult>(
|
return Navigator.of(context).push<DocumentUploadResult>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiProvider(
|
builder: (_) => MultiProvider(
|
||||||
@@ -361,6 +362,7 @@ Future<DocumentUploadResult?> pushDocumentUploadPreparationPage(
|
|||||||
Provider.value(value: labelRepo),
|
Provider.value(value: labelRepo),
|
||||||
Provider.value(value: docsApi),
|
Provider.value(value: docsApi),
|
||||||
Provider.value(value: connectivity),
|
Provider.value(value: connectivity),
|
||||||
|
Provider.value(value: apiVersion)
|
||||||
],
|
],
|
||||||
builder: (_, child) => BlocProvider(
|
builder: (_, child) => BlocProvider(
|
||||||
create: (_) => DocumentUploadCubit(
|
create: (_) => DocumentUploadCubit(
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ class LabelRepository extends PersistentRepository<LabelRepositoryState> {
|
|||||||
if (correspondent != null) {
|
if (correspondent != null) {
|
||||||
final updatedState = {...state.correspondents}..[id] = correspondent;
|
final updatedState = {...state.correspondents}..[id] = correspondent;
|
||||||
emit(state.copyWith(correspondents: updatedState));
|
emit(state.copyWith(correspondents: updatedState));
|
||||||
|
|
||||||
return correspondent;
|
return correspondent;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
||||||
|
import 'package:paperless_mobile/core/interceptor/dio_unauthorized_interceptor.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
|
|
||||||
/// Manages the security context, authentication and base request URL for
|
/// Manages the security context, authentication and base request URL for
|
||||||
/// an underlying [Dio] client which is injected into all services
|
/// an underlying [Dio] client which is injected into all services
|
||||||
/// requiring authenticated access to the Paperless HTTP API.
|
/// requiring authenticated access to the Paperless REST API.
|
||||||
class SessionManager extends ValueNotifier<Dio> {
|
class SessionManager extends ValueNotifier<Dio> {
|
||||||
Dio get client => value;
|
Dio get client => value;
|
||||||
|
|
||||||
@@ -20,16 +21,21 @@ class SessionManager extends ValueNotifier<Dio> {
|
|||||||
static Dio _initDio(List<Interceptor> interceptors) {
|
static Dio _initDio(List<Interceptor> interceptors) {
|
||||||
//en- and decoded by utf8 by default
|
//en- and decoded by utf8 by default
|
||||||
final Dio dio = Dio(
|
final Dio dio = Dio(
|
||||||
BaseOptions(contentType: Headers.jsonContentType),
|
BaseOptions(
|
||||||
|
contentType: Headers.jsonContentType,
|
||||||
|
followRedirects: true,
|
||||||
|
maxRedirects: 10,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
dio.options
|
dio.options
|
||||||
..receiveTimeout = const Duration(seconds: 30)
|
..receiveTimeout = const Duration(seconds: 30)
|
||||||
..sendTimeout = const Duration(seconds: 60)
|
..sendTimeout = const Duration(seconds: 60)
|
||||||
..responseType = ResponseType.json;
|
..responseType = ResponseType.json;
|
||||||
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient =
|
||||||
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
() => HttpClient()..badCertificateCallback = (cert, host, port) => true;
|
||||||
dio.interceptors.addAll([
|
dio.interceptors.addAll([
|
||||||
...interceptors,
|
...interceptors,
|
||||||
|
DioUnauthorizedInterceptor(),
|
||||||
DioHttpErrorInterceptor(),
|
DioHttpErrorInterceptor(),
|
||||||
PrettyDioLogger(
|
PrettyDioLogger(
|
||||||
compact: true,
|
compact: true,
|
||||||
@@ -64,7 +70,7 @@ class SessionManager extends ValueNotifier<Dio> {
|
|||||||
password: clientCertificate.passphrase,
|
password: clientCertificate.passphrase,
|
||||||
);
|
);
|
||||||
final adapter = IOHttpClientAdapter()
|
final adapter = IOHttpClientAdapter()
|
||||||
..onHttpClientCreate = (client) => HttpClient(context: context)
|
..createHttpClient = () => HttpClient(context: context)
|
||||||
..badCertificateCallback =
|
..badCertificateCallback =
|
||||||
(X509Certificate cert, String host, int port) => true;
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
return ReachabilityStatus.reachable;
|
return ReachabilityStatus.reachable;
|
||||||
}
|
}
|
||||||
return ReachabilityStatus.notReachable;
|
return ReachabilityStatus.notReachable;
|
||||||
} on DioError catch (error) {
|
} on DioException catch (error) {
|
||||||
if (error.type == DioErrorType.unknown &&
|
if (error.type == DioExceptionType.unknown &&
|
||||||
error.error is ReachabilityStatus) {
|
error.error is ReachabilityStatus) {
|
||||||
return error.error as ReachabilityStatus;
|
return error.error as ReachabilityStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class FileService {
|
|||||||
) async {
|
) async {
|
||||||
final dir = await documentsDirectory;
|
final dir = await documentsDirectory;
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
throw const PaperlessServerException.unknown(); //TODO: better handling
|
throw const PaperlessApiException.unknown(); //TODO: better handling
|
||||||
}
|
}
|
||||||
File file = File("${dir.path}/$filename");
|
File file = File("${dir.path}/$filename");
|
||||||
return file..writeAsBytes(bytes);
|
return file..writeAsBytes(bytes);
|
||||||
|
|||||||
@@ -3,74 +3,75 @@ import 'package:paperless_api/paperless_api.dart';
|
|||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
String translateError(BuildContext context, ErrorCode code) {
|
String translateError(BuildContext context, ErrorCode code) {
|
||||||
switch (code) {
|
return switch (code) {
|
||||||
case ErrorCode.unknown:
|
ErrorCode.unknown => S.of(context)!.anUnknownErrorOccurred,
|
||||||
return S.of(context)!.anUnknownErrorOccurred;
|
ErrorCode.authenticationFailed =>
|
||||||
case ErrorCode.authenticationFailed:
|
S.of(context)!.authenticationFailedPleaseTryAgain,
|
||||||
return S.of(context)!.authenticationFailedPleaseTryAgain;
|
ErrorCode.notAuthenticated => S.of(context)!.userIsNotAuthenticated,
|
||||||
case ErrorCode.notAuthenticated:
|
ErrorCode.documentUploadFailed => S.of(context)!.couldNotUploadDocument,
|
||||||
return S.of(context)!.userIsNotAuthenticated;
|
ErrorCode.documentUpdateFailed => S.of(context)!.couldNotUpdateDocument,
|
||||||
case ErrorCode.documentUploadFailed:
|
ErrorCode.documentLoadFailed => S.of(context)!.couldNotLoadDocuments,
|
||||||
return S.of(context)!.couldNotUploadDocument;
|
ErrorCode.documentDeleteFailed => S.of(context)!.couldNotDeleteDocument,
|
||||||
case ErrorCode.documentUpdateFailed:
|
ErrorCode.documentPreviewFailed =>
|
||||||
return S.of(context)!.couldNotUpdateDocument;
|
S.of(context)!.couldNotLoadDocumentPreview,
|
||||||
case ErrorCode.documentLoadFailed:
|
ErrorCode.documentAsnQueryFailed =>
|
||||||
return S.of(context)!.couldNotLoadDocuments;
|
S.of(context)!.couldNotAssignArchiveSerialNumber,
|
||||||
case ErrorCode.documentDeleteFailed:
|
ErrorCode.tagCreateFailed => S.of(context)!.couldNotCreateTag,
|
||||||
return S.of(context)!.couldNotDeleteDocument;
|
ErrorCode.tagLoadFailed => S.of(context)!.couldNotLoadTags,
|
||||||
case ErrorCode.documentPreviewFailed:
|
ErrorCode.documentTypeCreateFailed => S.of(context)!.couldNotCreateDocument,
|
||||||
return S.of(context)!.couldNotLoadDocumentPreview;
|
ErrorCode.documentTypeLoadFailed =>
|
||||||
case ErrorCode.documentAsnQueryFailed:
|
S.of(context)!.couldNotLoadDocumentTypes,
|
||||||
return S.of(context)!.couldNotAssignArchiveSerialNumber;
|
ErrorCode.correspondentCreateFailed =>
|
||||||
case ErrorCode.tagCreateFailed:
|
S.of(context)!.couldNotCreateCorrespondent,
|
||||||
return S.of(context)!.couldNotCreateTag;
|
ErrorCode.correspondentLoadFailed =>
|
||||||
case ErrorCode.tagLoadFailed:
|
S.of(context)!.couldNotLoadCorrespondents,
|
||||||
return S.of(context)!.couldNotLoadTags;
|
ErrorCode.scanRemoveFailed =>
|
||||||
case ErrorCode.documentTypeCreateFailed:
|
S.of(context)!.anErrorOccurredRemovingTheScans,
|
||||||
return S.of(context)!.couldNotCreateDocument;
|
ErrorCode.invalidClientCertificateConfiguration =>
|
||||||
case ErrorCode.documentTypeLoadFailed:
|
S.of(context)!.invalidCertificateOrMissingPassphrase,
|
||||||
return S.of(context)!.couldNotLoadDocumentTypes;
|
ErrorCode.documentBulkActionFailed =>
|
||||||
case ErrorCode.correspondentCreateFailed:
|
S.of(context)!.couldNotBulkEditDocuments,
|
||||||
return S.of(context)!.couldNotCreateCorrespondent;
|
ErrorCode.biometricsNotSupported =>
|
||||||
case ErrorCode.correspondentLoadFailed:
|
S.of(context)!.biometricAuthenticationNotSupported,
|
||||||
return S.of(context)!.couldNotLoadCorrespondents;
|
ErrorCode.biometricAuthenticationFailed =>
|
||||||
case ErrorCode.scanRemoveFailed:
|
S.of(context)!.biometricAuthenticationFailed,
|
||||||
return S.of(context)!.anErrorOccurredRemovingTheScans;
|
ErrorCode.deviceOffline => S.of(context)!.youAreCurrentlyOffline,
|
||||||
case ErrorCode.invalidClientCertificateConfiguration:
|
ErrorCode.serverUnreachable =>
|
||||||
return S.of(context)!.invalidCertificateOrMissingPassphrase;
|
S.of(context)!.couldNotReachYourPaperlessServer,
|
||||||
case ErrorCode.documentBulkActionFailed:
|
ErrorCode.similarQueryError => S.of(context)!.couldNotLoadSimilarDocuments,
|
||||||
return S.of(context)!.couldNotBulkEditDocuments;
|
ErrorCode.autocompleteQueryError =>
|
||||||
case ErrorCode.biometricsNotSupported:
|
S.of(context)!.anErrorOccurredWhileTryingToAutocompleteYourQuery,
|
||||||
return S.of(context)!.biometricAuthenticationNotSupported;
|
ErrorCode.storagePathLoadFailed => S.of(context)!.couldNotLoadStoragePaths,
|
||||||
case ErrorCode.biometricAuthenticationFailed:
|
ErrorCode.storagePathCreateFailed =>
|
||||||
return S.of(context)!.biometricAuthenticationFailed;
|
S.of(context)!.couldNotCreateStoragePath,
|
||||||
case ErrorCode.deviceOffline:
|
ErrorCode.loadSavedViewsError => S.of(context)!.couldNotLoadSavedViews,
|
||||||
return S.of(context)!.youAreCurrentlyOffline;
|
ErrorCode.createSavedViewError => S.of(context)!.couldNotCreateSavedView,
|
||||||
case ErrorCode.serverUnreachable:
|
ErrorCode.deleteSavedViewError => S.of(context)!.couldNotDeleteSavedView,
|
||||||
return S.of(context)!.couldNotReachYourPaperlessServer;
|
ErrorCode.requestTimedOut => S.of(context)!.requestTimedOut,
|
||||||
case ErrorCode.similarQueryError:
|
ErrorCode.unsupportedFileFormat => S.of(context)!.fileFormatNotSupported,
|
||||||
return S.of(context)!.couldNotLoadSimilarDocuments;
|
ErrorCode.missingClientCertificate =>
|
||||||
case ErrorCode.autocompleteQueryError:
|
S.of(context)!.aClientCertificateWasExpectedButNotSent,
|
||||||
return S.of(context)!.anErrorOccurredWhileTryingToAutocompleteYourQuery;
|
ErrorCode.suggestionsQueryError => S.of(context)!.couldNotLoadSuggestions,
|
||||||
case ErrorCode.storagePathLoadFailed:
|
ErrorCode.acknowledgeTasksError => S.of(context)!.couldNotAcknowledgeTasks,
|
||||||
return S.of(context)!.couldNotLoadStoragePaths;
|
ErrorCode.correspondentDeleteFailed =>
|
||||||
case ErrorCode.storagePathCreateFailed:
|
"Could not delete correspondent, please try again.",
|
||||||
return S.of(context)!.couldNotCreateStoragePath;
|
ErrorCode.documentTypeDeleteFailed =>
|
||||||
case ErrorCode.loadSavedViewsError:
|
"Could not delete document type, please try again.",
|
||||||
return S.of(context)!.couldNotLoadSavedViews;
|
ErrorCode.tagDeleteFailed => "Could not delete tag, please try again.",
|
||||||
case ErrorCode.createSavedViewError:
|
ErrorCode.correspondentUpdateFailed =>
|
||||||
return S.of(context)!.couldNotCreateSavedView;
|
"Could not update correspondent, please try again.",
|
||||||
case ErrorCode.deleteSavedViewError:
|
ErrorCode.documentTypeUpdateFailed =>
|
||||||
return S.of(context)!.couldNotDeleteSavedView;
|
"Could not update document type, please try again.",
|
||||||
case ErrorCode.requestTimedOut:
|
ErrorCode.tagUpdateFailed => "Could not update tag, please try again.",
|
||||||
return S.of(context)!.requestTimedOut;
|
ErrorCode.storagePathDeleteFailed =>
|
||||||
case ErrorCode.unsupportedFileFormat:
|
"Could not delete storage path, please try again.",
|
||||||
return S.of(context)!.fileFormatNotSupported;
|
ErrorCode.storagePathUpdateFailed =>
|
||||||
case ErrorCode.missingClientCertificate:
|
"Could not update storage path, please try again.",
|
||||||
return S.of(context)!.aClientCertificateWasExpectedButNotSent;
|
ErrorCode.serverInformationLoadFailed =>
|
||||||
case ErrorCode.suggestionsQueryError:
|
"Could not load server information.",
|
||||||
return S.of(context)!.couldNotLoadSuggestions;
|
ErrorCode.serverStatisticsLoadFailed => "Could not load server statistics.",
|
||||||
case ErrorCode.acknowledgeTasksError:
|
ErrorCode.uiSettingsLoadFailed => "Could not load UI settings",
|
||||||
return S.of(context)!.couldNotAcknowledgeTasks;
|
ErrorCode.loadTasksError => "Could not load tasks.",
|
||||||
}
|
ErrorCode.userNotFound => "User could not be found.",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
typedef JSON = Map<String, dynamic>;
|
|
||||||
typedef PaperlessValidationErrors = Map<String, String>;
|
|
||||||
typedef PaperlessLocalizedErrorMessage = String;
|
|
||||||
|
|
||||||
extension ValidationErrorsUtils on PaperlessValidationErrors {
|
|
||||||
bool get hasFieldUnspecificError => containsKey("non_field_errors");
|
|
||||||
String? get fieldUnspecificError => this['non_field_errors'];
|
|
||||||
}
|
|
||||||
@@ -70,13 +70,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
|
|
||||||
Future<void> loadFullContent() async {
|
Future<void> loadFullContent() async {
|
||||||
final doc = await _api.find(state.document.id);
|
final doc = await _api.find(state.document.id);
|
||||||
if (doc == null) {
|
emit(
|
||||||
return;
|
state.copyWith(
|
||||||
}
|
isFullContentLoaded: true,
|
||||||
emit(state.copyWith(
|
fullContent: doc.content,
|
||||||
isFullContentLoaded: true,
|
),
|
||||||
fullContent: doc.content,
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> assignAsn(
|
Future<void> assignAsn(
|
||||||
@@ -99,13 +98,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
|
|
||||||
Future<ResultType> openDocumentInSystemViewer() async {
|
Future<ResultType> openDocumentInSystemViewer() async {
|
||||||
final cacheDir = await FileService.temporaryDirectory;
|
final cacheDir = await FileService.temporaryDirectory;
|
||||||
//TODO: Why is this cleared here?
|
|
||||||
await FileService.clearDirectoryContent(PaperlessDirectoryType.temporary);
|
|
||||||
if (state.metaData == null) {
|
if (state.metaData == null) {
|
||||||
await loadMetaData();
|
await loadMetaData();
|
||||||
}
|
}
|
||||||
final desc = FileDescription.fromPath(
|
final desc = FileDescription.fromPath(
|
||||||
state.metaData!.mediaFilename.replaceAll("/", " "));
|
state.metaData!.mediaFilename.replaceAll("/", " "),
|
||||||
|
);
|
||||||
|
|
||||||
final fileName = "${desc.filename}.pdf";
|
final fileName = "${desc.filename}.pdf";
|
||||||
final file = File("${cacheDir.path}/$fileName");
|
final file = File("${cacheDir.path}/$fileName");
|
||||||
|
|||||||
@@ -287,8 +287,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
Widget _buildEditButton() {
|
Widget _buildEditButton() {
|
||||||
bool canEdit = context.watchInternetConnection &&
|
bool canEdit = context.watchInternetConnection &&
|
||||||
LocalUserAccount.current.paperlessUser
|
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
|
||||||
if (!canEdit) {
|
if (!canEdit) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@@ -319,8 +318,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
final isConnected = connectivityState.isConnected;
|
final isConnected = connectivityState.isConnected;
|
||||||
|
|
||||||
final canDelete = isConnected &&
|
final canDelete = isConnected &&
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||||
PermissionAction.delete, PermissionTarget.document);
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -430,7 +428,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
try {
|
try {
|
||||||
await context.read<DocumentDetailsCubit>().delete(document);
|
await context.read<DocumentDetailsCubit>().delete(document);
|
||||||
showSnackBar(context, S.of(context)!.documentSuccessfullyDeleted);
|
showSnackBar(context, S.of(context)!.documentSuccessfullyDeleted);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
// Document deleted => go back to primary route
|
// Document deleted => go back to primary route
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
@@ -48,10 +47,7 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userCanEditDocument =
|
final userCanEditDocument =
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||||
PermissionAction.change,
|
|
||||||
PermissionTarget.document,
|
|
||||||
);
|
|
||||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.document.archiveSerialNumber !=
|
previous.document.archiveSerialNumber !=
|
||||||
@@ -124,12 +120,14 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
.read<DocumentDetailsCubit>()
|
.read<DocumentDetailsCubit>()
|
||||||
.assignAsn(widget.document, asn: asn)
|
.assignAsn(widget.document, asn: asn)
|
||||||
.then((value) => _onAsnUpdated())
|
.then((value) => _onAsnUpdated())
|
||||||
.onError<PaperlessServerException>(
|
.onError<PaperlessApiException>(
|
||||||
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
||||||
)
|
)
|
||||||
.onError<PaperlessValidationErrors>(
|
.onError<PaperlessFormValidationException>(
|
||||||
(error, stackTrace) => setState(() => _errors = error),
|
(error, stackTrace) {
|
||||||
);
|
setState(() => _errors = error.validationMessages);
|
||||||
|
},
|
||||||
|
);
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,9 +139,10 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
autoAssign: true,
|
autoAssign: true,
|
||||||
)
|
)
|
||||||
.then((value) => _onAsnUpdated())
|
.then((value) => _onAsnUpdated())
|
||||||
.onError<PaperlessServerException>(
|
.onError<PaperlessApiException>(
|
||||||
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
(error, stackTrace) => showErrorMessage(context, error, stackTrace),
|
||||||
);
|
)
|
||||||
|
.catchError((error) => showGenericError(context, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAsnUpdated() {
|
void _onAsnUpdated() {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
|||||||
locale: globalSettings.preferredLocaleSubtag,
|
locale: globalSettings.preferredLocaleSubtag,
|
||||||
);
|
);
|
||||||
// showSnackBar(context, S.of(context)!.documentSuccessfullyDownloaded);
|
// showSnackBar(context, S.of(context)!.documentSuccessfullyDownloaded);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showGenericError(context, error);
|
showGenericError(context, error);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||||
@@ -45,38 +46,35 @@ class DocumentOverviewWidget extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
label: S.of(context)!.createdAt,
|
label: S.of(context)!.createdAt,
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
Visibility(
|
if (document.documentType != null &&
|
||||||
visible: document.documentType != null,
|
LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||||
child: DetailsItem(
|
DetailsItem(
|
||||||
label: S.of(context)!.documentType,
|
label: S.of(context)!.documentType,
|
||||||
content: LabelText<DocumentType>(
|
content: LabelText<DocumentType>(
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
label: availableDocumentTypes[document.documentType],
|
label: availableDocumentTypes[document.documentType],
|
||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
),
|
if (document.correspondent != null &&
|
||||||
Visibility(
|
LocalUserAccount.current.paperlessUser.canViewCorrespondents)
|
||||||
visible: document.correspondent != null,
|
DetailsItem(
|
||||||
child: DetailsItem(
|
|
||||||
label: S.of(context)!.correspondent,
|
label: S.of(context)!.correspondent,
|
||||||
content: LabelText<Correspondent>(
|
content: LabelText<Correspondent>(
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
label: availableCorrespondents[document.correspondent],
|
label: availableCorrespondents[document.correspondent],
|
||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
),
|
if (document.storagePath != null &&
|
||||||
Visibility(
|
LocalUserAccount.current.paperlessUser.canViewStoragePaths)
|
||||||
visible: document.storagePath != null,
|
DetailsItem(
|
||||||
child: DetailsItem(
|
|
||||||
label: S.of(context)!.storagePath,
|
label: S.of(context)!.storagePath,
|
||||||
content: LabelText<StoragePath>(
|
content: LabelText<StoragePath>(
|
||||||
label: availableStoragePaths[document.storagePath],
|
label: availableStoragePaths[document.storagePath],
|
||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
),
|
if (document.tags.isNotEmpty &&
|
||||||
Visibility(
|
LocalUserAccount.current.paperlessUser.canViewTags)
|
||||||
visible: document.tags.isNotEmpty,
|
DetailsItem(
|
||||||
child: DetailsItem(
|
|
||||||
label: S.of(context)!.tags,
|
label: S.of(context)!.tags,
|
||||||
content: Padding(
|
content: Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
@@ -86,7 +84,6 @@ class DocumentOverviewWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class _DocumentShareButtonState extends State<DocumentShareButton> {
|
|||||||
await context.read<DocumentDetailsCubit>().shareDocument(
|
await context.read<DocumentDetailsCubit>().shareDocument(
|
||||||
shareOriginal: original,
|
shareOriginal: original,
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showGenericError(context, error);
|
showGenericError(context, error);
|
||||||
|
|||||||
@@ -123,12 +123,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
name: fkCorrespondent,
|
name: fkCorrespondent,
|
||||||
prefixIcon: const Icon(Icons.person_outlined),
|
prefixIcon: const Icon(Icons.person_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount
|
canCreateNewLabel: LocalUserAccount.current
|
||||||
.current.paperlessUser
|
.paperlessUser.canCreateCorrespondents,
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.correspondent,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions
|
if (_filteredSuggestions
|
||||||
?.hasSuggestedCorrespondents ??
|
?.hasSuggestedCorrespondents ??
|
||||||
@@ -164,12 +160,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
initialName: currentInput,
|
initialName: currentInput,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canCreateNewLabel: LocalUserAccount
|
canCreateNewLabel: LocalUserAccount.current
|
||||||
.current.paperlessUser
|
.paperlessUser.canCreateDocumentTypes,
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.documentType,
|
|
||||||
),
|
|
||||||
addLabelText: S.of(context)!.addDocumentType,
|
addLabelText: S.of(context)!.addDocumentType,
|
||||||
labelText: S.of(context)!.documentType,
|
labelText: S.of(context)!.documentType,
|
||||||
initialValue:
|
initialValue:
|
||||||
@@ -214,12 +206,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
child: AddStoragePathPage(
|
child: AddStoragePathPage(
|
||||||
initalName: initialValue),
|
initalName: initialValue),
|
||||||
),
|
),
|
||||||
canCreateNewLabel: LocalUserAccount
|
canCreateNewLabel: LocalUserAccount.current
|
||||||
.current.paperlessUser
|
.paperlessUser.canCreateStoragePaths,
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.storagePath,
|
|
||||||
),
|
|
||||||
addLabelText: S.of(context)!.addStoragePath,
|
addLabelText: S.of(context)!.addStoragePath,
|
||||||
labelText: S.of(context)!.storagePath,
|
labelText: S.of(context)!.storagePath,
|
||||||
options: state.storagePaths,
|
options: state.storagePaths,
|
||||||
@@ -328,7 +316,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
try {
|
try {
|
||||||
await context.read<DocumentEditCubit>().updateDocument(mergedDocument);
|
await context.read<DocumentEditCubit>().updateDocument(mergedDocument);
|
||||||
showSnackBar(context, S.of(context)!.documentSuccessfullyUpdated);
|
showSnackBar(context, S.of(context)!.documentSuccessfullyUpdated);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
|||||||
scans.removeAt(fileIndex);
|
scans.removeAt(fileIndex);
|
||||||
emit(scans);
|
emit(scans);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
throw const PaperlessServerException(ErrorCode.scanRemoveFailed);
|
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
|||||||
imageCache.clear();
|
imageCache.clear();
|
||||||
emit([]);
|
emit([]);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
throw const PaperlessServerException(ErrorCode.scanRemoveFailed);
|
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
handle: searchBarHandle,
|
handle: searchBarHandle,
|
||||||
sliver: const SliverSearchBar(),
|
sliver: SliverSearchBar(
|
||||||
|
titleText: S.of(context)!.scanner,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
handle: actionsHandle,
|
handle: actionsHandle,
|
||||||
@@ -322,7 +324,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
onDelete: () async {
|
onDelete: () async {
|
||||||
try {
|
try {
|
||||||
context.read<DocumentScannerCubit>().removeScan(index);
|
context.read<DocumentScannerCubit>().removeScan(index);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -339,7 +341,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
void _reset(BuildContext context) {
|
void _reset(BuildContext context) {
|
||||||
try {
|
try {
|
||||||
context.read<DocumentScannerCubit>().reset();
|
context.read<DocumentScannerCubit>().reset();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,7 +362,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
)) {
|
)) {
|
||||||
showErrorMessage(
|
showErrorMessage(
|
||||||
context,
|
context,
|
||||||
const PaperlessServerException(ErrorCode.unsupportedFileFormat),
|
const PaperlessApiException(ErrorCode.unsupportedFileFormat),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,74 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:hive_flutter/adapters.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/view/document_search_bar.dart';
|
import 'package:paperless_mobile/features/document_search/view/document_search_bar.dart';
|
||||||
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class SliverSearchBar extends StatelessWidget {
|
class SliverSearchBar extends StatelessWidget {
|
||||||
final bool floating;
|
final bool floating;
|
||||||
final bool pinned;
|
final bool pinned;
|
||||||
|
final String titleText;
|
||||||
const SliverSearchBar({
|
const SliverSearchBar({
|
||||||
super.key,
|
super.key,
|
||||||
this.floating = false,
|
this.floating = false,
|
||||||
this.pinned = false,
|
this.pinned = false,
|
||||||
|
required this.titleText,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverPadding(
|
if (LocalUserAccount.current.paperlessUser.canViewDocuments) {
|
||||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
return SliverAppBar(
|
||||||
sliver: SliverPersistentHeader(
|
toolbarHeight: kToolbarHeight,
|
||||||
floating: floating,
|
flexibleSpace: Container(
|
||||||
pinned: pinned,
|
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
child: const DocumentSearchBar(),
|
||||||
minExtent: kToolbarHeight,
|
|
||||||
maxExtent: kToolbarHeight,
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: const DocumentSearchBar(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
automaticallyImplyLeading: false,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return SliverAppBar(
|
||||||
|
title: Text(titleText),
|
||||||
|
actions: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12.0),
|
||||||
|
child: IconButton(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
icon: GlobalSettingsBuilder(
|
||||||
|
builder: (context, settings) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable:
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
|
.listenable(),
|
||||||
|
builder: (context, box, _) {
|
||||||
|
final account = box.get(settings.currentLoggedInUser!)!;
|
||||||
|
return UserAvatar(account: account);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
final apiVersion = context.read<ApiVersion>();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Provider.value(
|
||||||
|
value: apiVersion,
|
||||||
|
child: const ManageAccountsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
|||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||||
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentUploadResult {
|
class DocumentUploadResult {
|
||||||
final bool success;
|
final bool success;
|
||||||
@@ -56,7 +57,7 @@ class _DocumentUploadPreparationPageState
|
|||||||
|
|
||||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||||
|
|
||||||
PaperlessValidationErrors _errors = {};
|
Map<String, String> _errors = {};
|
||||||
bool _isUploadLoading = false;
|
bool _isUploadLoading = false;
|
||||||
late bool _syncTitleAndFilename;
|
late bool _syncTitleAndFilename;
|
||||||
bool _showDatePickerDeleteIcon = false;
|
bool _showDatePickerDeleteIcon = false;
|
||||||
@@ -197,54 +198,64 @@ class _DocumentUploadPreparationPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Correspondent
|
// Correspondent
|
||||||
LabelFormField<Correspondent>(
|
if (LocalUserAccount
|
||||||
showAnyAssignedOption: false,
|
.current.paperlessUser.canViewCorrespondents)
|
||||||
showNotAssignedOption: false,
|
LabelFormField<Correspondent>(
|
||||||
addLabelPageBuilder: (initialName) =>
|
showAnyAssignedOption: false,
|
||||||
RepositoryProvider.value(
|
showNotAssignedOption: false,
|
||||||
value: context.read<LabelRepository>(),
|
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||||
child: AddCorrespondentPage(initialName: initialName),
|
providers: [
|
||||||
|
Provider.value(
|
||||||
|
value: context.read<LabelRepository>(),
|
||||||
|
),
|
||||||
|
Provider.value(
|
||||||
|
value: context.read<ApiVersion>(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
child: AddCorrespondentPage(initialName: initialName),
|
||||||
|
),
|
||||||
|
addLabelText: S.of(context)!.addCorrespondent,
|
||||||
|
labelText: S.of(context)!.correspondent + " *",
|
||||||
|
name: DocumentModel.correspondentKey,
|
||||||
|
options: state.correspondents,
|
||||||
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
|
allowSelectUnassigned: true,
|
||||||
|
canCreateNewLabel: LocalUserAccount
|
||||||
|
.current.paperlessUser.canCreateCorrespondents,
|
||||||
),
|
),
|
||||||
addLabelText: S.of(context)!.addCorrespondent,
|
|
||||||
labelText: S.of(context)!.correspondent + " *",
|
|
||||||
name: DocumentModel.correspondentKey,
|
|
||||||
options: state.correspondents,
|
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
|
||||||
allowSelectUnassigned: true,
|
|
||||||
canCreateNewLabel:
|
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.correspondent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Document type
|
// Document type
|
||||||
LabelFormField<DocumentType>(
|
if (LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||||
showAnyAssignedOption: false,
|
LabelFormField<DocumentType>(
|
||||||
showNotAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
addLabelPageBuilder: (initialName) =>
|
showNotAssignedOption: false,
|
||||||
RepositoryProvider.value(
|
addLabelPageBuilder: (initialName) => MultiProvider(
|
||||||
value: context.read<LabelRepository>(),
|
providers: [
|
||||||
child: AddDocumentTypePage(initialName: initialName),
|
Provider.value(
|
||||||
|
value: context.read<LabelRepository>(),
|
||||||
|
),
|
||||||
|
Provider.value(
|
||||||
|
value: context.read<ApiVersion>(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
child: AddDocumentTypePage(initialName: initialName),
|
||||||
|
),
|
||||||
|
addLabelText: S.of(context)!.addDocumentType,
|
||||||
|
labelText: S.of(context)!.documentType + " *",
|
||||||
|
name: DocumentModel.documentTypeKey,
|
||||||
|
options: state.documentTypes,
|
||||||
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
|
allowSelectUnassigned: true,
|
||||||
|
canCreateNewLabel: LocalUserAccount
|
||||||
|
.current.paperlessUser.canCreateDocumentTypes,
|
||||||
),
|
),
|
||||||
addLabelText: S.of(context)!.addDocumentType,
|
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||||
labelText: S.of(context)!.documentType + " *",
|
TagsFormField(
|
||||||
name: DocumentModel.documentTypeKey,
|
name: DocumentModel.tagsKey,
|
||||||
options: state.documentTypes,
|
allowCreation: true,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
allowExclude: false,
|
||||||
allowSelectUnassigned: true,
|
allowOnlySelection: true,
|
||||||
canCreateNewLabel:
|
options: state.tags,
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.documentType,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
TagsFormField(
|
|
||||||
name: DocumentModel.tagsKey,
|
|
||||||
allowCreation: true,
|
|
||||||
allowExclude: false,
|
|
||||||
allowOnlySelection: true,
|
|
||||||
options: state.tags,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
"* " + S.of(context)!.uploadInferValuesHint,
|
"* " + S.of(context)!.uploadInferValuesHint,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
@@ -301,14 +312,14 @@ class _DocumentUploadPreparationPageState
|
|||||||
context,
|
context,
|
||||||
DocumentUploadResult(true, taskId),
|
DocumentUploadResult(true, taskId),
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} on PaperlessValidationErrors catch (errors) {
|
} on PaperlessFormValidationException catch (exception) {
|
||||||
setState(() => _errors = errors);
|
setState(() => _errors = exception.validationMessages);
|
||||||
} catch (unknownError, stackTrace) {
|
} catch (unknownError, stackTrace) {
|
||||||
debugPrint(unknownError.toString());
|
debugPrint(unknownError.toString());
|
||||||
showErrorMessage(
|
showErrorMessage(
|
||||||
context, const PaperlessServerException.unknown(), stackTrace);
|
context, const PaperlessApiException.unknown(), stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isUploadLoading = false;
|
_isUploadLoading = false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
||||||
@@ -53,14 +54,16 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
final showSavedViews =
|
||||||
|
LocalUserAccount.current.paperlessUser.canViewSavedViews;
|
||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: 2,
|
length: showSavedViews ? 2 : 1,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
Future.wait([
|
Future.wait([
|
||||||
context.read<DocumentsCubit>().reload(),
|
context.read<DocumentsCubit>().reload(),
|
||||||
context.read<SavedViewCubit>().reload(),
|
context.read<SavedViewCubit>().reload(),
|
||||||
]).onError<PaperlessServerException>(
|
]).onError<PaperlessApiException>(
|
||||||
(error, stackTrace) {
|
(error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
return [];
|
return [];
|
||||||
@@ -105,7 +108,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
try {
|
try {
|
||||||
context.read<DocumentsCubit>().reload();
|
context.read<DocumentsCubit>().reload();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -197,7 +200,10 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.selection.isEmpty) {
|
if (state.selection.isEmpty) {
|
||||||
return const SliverSearchBar(floating: true);
|
return SliverSearchBar(
|
||||||
|
floating: true,
|
||||||
|
titleText: S.of(context)!.documents,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return DocumentSelectionSliverAppBar(
|
return DocumentSelectionSliverAppBar(
|
||||||
state: state,
|
state: state,
|
||||||
@@ -226,7 +232,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: S.of(context)!.documents),
|
Tab(text: S.of(context)!.documents),
|
||||||
Tab(text: S.of(context)!.views),
|
if (LocalUserAccount.current.paperlessUser
|
||||||
|
.canViewSavedViews)
|
||||||
|
Tab(text: S.of(context)!.views),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -268,14 +276,16 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Builder(
|
if (LocalUserAccount
|
||||||
builder: (context) {
|
.current.paperlessUser.canViewSavedViews)
|
||||||
return _buildSavedViewsTab(
|
Builder(
|
||||||
connectivityState,
|
builder: (context) {
|
||||||
context,
|
return _buildSavedViewsTab(
|
||||||
);
|
connectivityState,
|
||||||
},
|
context,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -334,7 +344,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
context
|
context
|
||||||
.read<DocumentsCubit>()
|
.read<DocumentsCubit>()
|
||||||
.loadMore()
|
.loadMore()
|
||||||
.onError<PaperlessServerException>(
|
.onError<PaperlessApiException>(
|
||||||
(error, stackTrace) => showErrorMessage(
|
(error, stackTrace) => showErrorMessage(
|
||||||
context,
|
context,
|
||||||
error,
|
error,
|
||||||
@@ -419,7 +429,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
if (newView != null) {
|
if (newView != null) {
|
||||||
try {
|
try {
|
||||||
await context.read<SavedViewCubit>().add(newView);
|
await context.read<SavedViewCubit>().add(newView);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,7 +482,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
.read<DocumentsCubit>()
|
.read<DocumentsCubit>()
|
||||||
.updateFilter(filter: filterIntent.filter!);
|
.updateFilter(filter: filterIntent.filter!);
|
||||||
}
|
}
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +534,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,7 +565,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,7 +596,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,7 +627,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,7 +636,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
try {
|
try {
|
||||||
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
||||||
await context.read<DocumentsCubit>().reload();
|
await context.read<DocumentsCubit>().reload();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,7 +645,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
try {
|
try {
|
||||||
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
|
||||||
await context.read<SavedViewCubit>().reload();
|
await context.read<SavedViewCubit>().reload();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,10 +160,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
initialValue: widget.initialFilter.documentType,
|
initialValue: widget.initialFilter.documentType,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
canCreateNewLabel:
|
||||||
PermissionAction.add,
|
LocalUserAccount.current.paperlessUser.canCreateDocumentTypes,
|
||||||
PermissionTarget.documentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,10 +173,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
initialValue: widget.initialFilter.correspondent,
|
initialValue: widget.initialFilter.correspondent,
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
canCreateNewLabel:
|
||||||
PermissionAction.add,
|
LocalUserAccount.current.paperlessUser.canCreateCorrespondents,
|
||||||
PermissionTarget.correspondent,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,10 +186,8 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
initialValue: widget.initialFilter.storagePath,
|
initialValue: widget.initialFilter.storagePath,
|
||||||
prefixIcon: const Icon(Icons.folder_outlined),
|
prefixIcon: const Icon(Icons.folder_outlined),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
canCreateNewLabel:
|
||||||
PermissionAction.add,
|
LocalUserAccount.current.paperlessUser.canCreateStoragePaths,
|
||||||
PermissionTarget.storagePath,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
|||||||
S.of(context)!.documentsSuccessfullyDeleted,
|
S.of(context)!.documentsSuccessfullyDeleted,
|
||||||
);
|
);
|
||||||
context.read<DocumentsCubit>().resetSelection();
|
context.read<DocumentsCubit>().resetSelection();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
|||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
try {
|
try {
|
||||||
onDelete(context, label);
|
onDelete(context, label);
|
||||||
} on PaperlessServerException catch (error) {
|
} on PaperlessApiException catch (error) {
|
||||||
showErrorMessage(context, error);
|
showErrorMessage(context, error);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
log("An error occurred!", error: error, stackTrace: stackTrace);
|
log("An error occurred!", error: error, stackTrace: stackTrace);
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ class EditCorrespondentPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete:
|
||||||
PermissionAction.delete,
|
LocalUserAccount.current.paperlessUser.canDeleteCorrespondents,
|
||||||
PermissionTarget.correspondent,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ class EditDocumentTypePage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete:
|
||||||
PermissionAction.delete,
|
LocalUserAccount.current.paperlessUser.canDeleteDocumentTypes,
|
||||||
PermissionTarget.documentType,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ class EditStoragePathPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.canDeleteStoragePaths,
|
||||||
PermissionAction.delete,
|
|
||||||
PermissionTarget.storagePath,
|
|
||||||
),
|
|
||||||
additionalFields: [
|
additionalFields: [
|
||||||
StoragePathAutofillFormBuilderField(
|
StoragePathAutofillFormBuilderField(
|
||||||
name: StoragePath.pathKey,
|
name: StoragePath.pathKey,
|
||||||
|
|||||||
@@ -26,10 +26,7 @@ class EditTagPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceTag(label),
|
context.read<EditLabelCubit>().replaceTag(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeTag(label),
|
context.read<EditLabelCubit>().removeTag(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.canDeleteTags,
|
||||||
PermissionAction.delete,
|
|
||||||
PermissionTarget.tag,
|
|
||||||
),
|
|
||||||
additionalFields: [
|
additionalFields: [
|
||||||
FormBuilderColorPickerField(
|
FormBuilderColorPickerField(
|
||||||
initialValue: tag.color,
|
initialValue: tag.color,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
|||||||
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
@@ -54,7 +53,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
|||||||
|
|
||||||
late bool _enableMatchFormField;
|
late bool _enableMatchFormField;
|
||||||
|
|
||||||
PaperlessValidationErrors _errors = {};
|
Map<String, String> _errors = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -69,7 +68,8 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
||||||
getSelectableMatchingAlgorithmValues(
|
getSelectableMatchingAlgorithmValues(
|
||||||
context.watch<ApiVersion>().hasMultiUserSupport);
|
context.watch<ApiVersion>().hasMultiUserSupport,
|
||||||
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
@@ -168,10 +168,10 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
|||||||
final parsed = widget.fromJsonT(mergedJson);
|
final parsed = widget.fromJsonT(mergedJson);
|
||||||
final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
|
final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
|
||||||
Navigator.pop(context, createdLabel);
|
Navigator.pop(context, createdLabel);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} on PaperlessValidationErrors catch (errors) {
|
} on PaperlessFormValidationException catch (exception) {
|
||||||
setState(() => _errors = errors);
|
setState(() => _errors = exception.validationMessages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ class HomePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
late Timer _inboxTimer;
|
Timer? _inboxTimer;
|
||||||
late final StreamSubscription _shareMediaSubscription;
|
late final StreamSubscription _shareMediaSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_listenToInboxChanges();
|
|
||||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser!;
|
.currentLoggedInUser!;
|
||||||
@@ -73,13 +73,15 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _listenToInboxChanges() {
|
void _listenToInboxChanges() {
|
||||||
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
if (LocalUserAccount.current.paperlessUser.canViewTags) {
|
||||||
if (!mounted) {
|
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||||||
timer.cancel();
|
if (!mounted) {
|
||||||
} else {
|
timer.cancel();
|
||||||
context.read<InboxCubit>().refreshItemsInInboxCount();
|
} else {
|
||||||
}
|
context.read<InboxCubit>().refreshItemsInInboxCount();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -89,7 +91,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
log('App is now in foreground');
|
log('App is now in foreground');
|
||||||
context.read<ConnectivityCubit>().reload();
|
context.read<ConnectivityCubit>().reload();
|
||||||
log("Reloaded device connectivity state");
|
log("Reloaded device connectivity state");
|
||||||
if (!_inboxTimer.isActive) {
|
if (!(_inboxTimer?.isActive ?? true)) {
|
||||||
_listenToInboxChanges();
|
_listenToInboxChanges();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -98,7 +100,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
case AppLifecycleState.detached:
|
case AppLifecycleState.detached:
|
||||||
default:
|
default:
|
||||||
log('App is now in background');
|
log('App is now in background');
|
||||||
_inboxTimer.cancel();
|
_inboxTimer?.cancel();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_inboxTimer.cancel();
|
_inboxTimer?.cancel();
|
||||||
_shareMediaSubscription.cancel();
|
_shareMediaSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -158,8 +160,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LocalUserAccount.current.paperlessUser
|
if (!LocalUserAccount.current.paperlessUser.canCreateDocuments) {
|
||||||
.hasPermission(PermissionAction.add, PermissionTarget.document)) {
|
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: "You do not have the permissions to upload documents.",
|
msg: "You do not have the permissions to upload documents.",
|
||||||
);
|
);
|
||||||
@@ -200,8 +201,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
label: S.of(context)!.documents,
|
label: S.of(context)!.documents,
|
||||||
),
|
),
|
||||||
if (LocalUserAccount.current.paperlessUser
|
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||||
.hasPermission(PermissionAction.add, PermissionTarget.document))
|
|
||||||
RouteDescription(
|
RouteDescription(
|
||||||
icon: const Icon(Icons.document_scanner_outlined),
|
icon: const Icon(Icons.document_scanner_outlined),
|
||||||
selectedIcon: Icon(
|
selectedIcon: Icon(
|
||||||
@@ -218,33 +218,31 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
label: S.of(context)!.labels,
|
label: S.of(context)!.labels,
|
||||||
),
|
),
|
||||||
RouteDescription(
|
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||||
icon: const Icon(Icons.inbox_outlined),
|
RouteDescription(
|
||||||
selectedIcon: Icon(
|
icon: const Icon(Icons.inbox_outlined),
|
||||||
Icons.inbox,
|
selectedIcon: Icon(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
Icons.inbox,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
label: S.of(context)!.inbox,
|
||||||
|
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Badge.count(
|
||||||
|
isLabelVisible: state.itemsInInboxCount > 0,
|
||||||
|
count: state.itemsInInboxCount,
|
||||||
|
child: icon,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
label: S.of(context)!.inbox,
|
|
||||||
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Badge.count(
|
|
||||||
isLabelVisible: state.itemsInInboxCount > 0,
|
|
||||||
count: state.itemsInInboxCount,
|
|
||||||
child: icon,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
final routes = <Widget>[
|
final routes = <Widget>[
|
||||||
const DocumentsPage(),
|
const DocumentsPage(),
|
||||||
if (LocalUserAccount.current.paperlessUser.hasPermission(
|
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.document,
|
|
||||||
))
|
|
||||||
const ScannerPage(),
|
const ScannerPage(),
|
||||||
const LabelsPage(),
|
const LabelsPage(),
|
||||||
const InboxPage(),
|
if (LocalUserAccount.current.paperlessUser.canViewTags) const InboxPage(),
|
||||||
];
|
];
|
||||||
return MultiBlocListener(
|
return MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
@@ -43,7 +44,14 @@ class HomeRoute extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GlobalSettingsBuilder(
|
return GlobalSettingsBuilder(
|
||||||
builder: (context, settings) {
|
builder: (context, settings) {
|
||||||
final currentLocalUserId = settings.currentLoggedInUser!;
|
final currentLocalUserId = settings.currentLoggedInUser;
|
||||||
|
if (currentLocalUserId == null) {
|
||||||
|
// This is the case when the current user logs out of the app.
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final currentUser =
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
|
.get(currentLocalUserId)!;
|
||||||
final apiVersion = ApiVersion(paperlessApiVersion);
|
final apiVersion = ApiVersion(paperlessApiVersion);
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -104,12 +112,31 @@ class HomeRoute extends StatelessWidget {
|
|||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
||||||
update: (context, value, previous) =>
|
update: (context, value, previous) {
|
||||||
LabelRepository(value)..initialize(),
|
final repo = LabelRepository(value);
|
||||||
|
if (currentUser.paperlessUser.canViewCorrespondents) {
|
||||||
|
repo.findAllCorrespondents();
|
||||||
|
}
|
||||||
|
if (currentUser.paperlessUser.canViewDocumentTypes) {
|
||||||
|
repo.findAllDocumentTypes();
|
||||||
|
}
|
||||||
|
if (currentUser.paperlessUser.canViewTags) {
|
||||||
|
repo.findAllTags();
|
||||||
|
}
|
||||||
|
if (currentUser.paperlessUser.canViewStoragePaths) {
|
||||||
|
repo.findAllStoragePaths();
|
||||||
|
}
|
||||||
|
return repo;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
||||||
update: (context, value, previous) =>
|
update: (context, value, previous) {
|
||||||
SavedViewRepository(value)..initialize(),
|
final repo = SavedViewRepository(value);
|
||||||
|
if (currentUser.paperlessUser.canViewSavedViews) {
|
||||||
|
repo.initialize();
|
||||||
|
}
|
||||||
|
return repo;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final canEditDocument = LocalUserAccount.current.paperlessUser
|
final canEditDocument =
|
||||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||||
@@ -65,7 +65,9 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
handle: searchBarHandle,
|
handle: searchBarHandle,
|
||||||
sliver: const SliverSearchBar(),
|
sliver: SliverSearchBar(
|
||||||
|
titleText: S.of(context)!.inbox,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
body: BlocBuilder<InboxCubit, InboxState>(
|
body: BlocBuilder<InboxCubit, InboxState>(
|
||||||
@@ -222,14 +224,14 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} on ServerMessageException catch (error) {
|
} on ServerMessageException catch (error) {
|
||||||
showGenericError(context, error.message);
|
showGenericError(context, error.message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorMessage(
|
showErrorMessage(
|
||||||
context,
|
context,
|
||||||
const PaperlessServerException.unknown(),
|
const PaperlessApiException.unknown(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -243,7 +245,7 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
await context
|
await context
|
||||||
.read<InboxCubit>()
|
.read<InboxCubit>()
|
||||||
.undoRemoveFromInbox(document, removedTags);
|
.undoRemoveFromInbox(document, removedTags);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,10 +238,8 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActions(BuildContext context) {
|
Widget _buildActions(BuildContext context) {
|
||||||
final canEdit = LocalUserAccount.current.paperlessUser
|
final canEdit = LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||||
.hasPermission(PermissionAction.change, PermissionTarget.document);
|
final canDelete = LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||||
final canDelete = LocalUserAccount.current.paperlessUser
|
|
||||||
.hasPermission(PermissionAction.delete, PermissionTarget.document);
|
|
||||||
final chipShape = RoundedRectangleBorder(
|
final chipShape = RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(32),
|
borderRadius: BorderRadius.circular(32),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,10 +73,7 @@ class TagsFormField extends StatelessWidget {
|
|||||||
initialValue: field.value,
|
initialValue: field.value,
|
||||||
allowOnlySelection: allowOnlySelection,
|
allowOnlySelection: allowOnlySelection,
|
||||||
allowCreation: allowCreation &&
|
allowCreation: allowCreation &&
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
LocalUserAccount.current.paperlessUser.canCreateTags,
|
||||||
PermissionAction.add,
|
|
||||||
PermissionTarget.tag,
|
|
||||||
),
|
|
||||||
allowExclude: allowExclude,
|
allowExclude: allowExclude,
|
||||||
),
|
),
|
||||||
onClosed: (data) {
|
onClosed: (data) {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
@@ -39,291 +42,327 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
|
||||||
|
int _calculateTabCount(UserModel user) => [
|
||||||
|
user.canViewCorrespondents,
|
||||||
|
user.canViewDocumentTypes,
|
||||||
|
user.canViewTags,
|
||||||
|
user.canViewStoragePaths,
|
||||||
|
].fold(0, (value, element) => value + (element ? 1 : 0));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
final user = LocalUserAccount.current.paperlessUser;
|
||||||
_tabController = TabController(length: 4, vsync: this)
|
_tabController = TabController(
|
||||||
|
length: _calculateTabCount(user), vsync: this)
|
||||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return ValueListenableBuilder(
|
||||||
length: 3,
|
valueListenable:
|
||||||
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
||||||
builder: (context, connectedState) {
|
builder: (context, box, child) {
|
||||||
return SafeArea(
|
final currentUserId =
|
||||||
child: Scaffold(
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
drawer: const AppDrawer(),
|
.getValue()!
|
||||||
floatingActionButton: FloatingActionButton(
|
.currentLoggedInUser;
|
||||||
onPressed: [
|
final user = box.get(currentUserId)!.paperlessUser;
|
||||||
_openAddCorrespondentPage,
|
|
||||||
_openAddDocumentTypePage,
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
_openAddTagPage,
|
builder: (context, connectedState) {
|
||||||
_openAddStoragePathPage,
|
return SafeArea(
|
||||||
][_currentIndex],
|
child: Scaffold(
|
||||||
child: const Icon(Icons.add),
|
drawer: const AppDrawer(),
|
||||||
),
|
floatingActionButton: FloatingActionButton(
|
||||||
body: NestedScrollView(
|
onPressed: [
|
||||||
floatHeaderSlivers: true,
|
if (user.canViewCorrespondents) _openAddCorrespondentPage,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
if (user.canViewDocumentTypes) _openAddDocumentTypePage,
|
||||||
SliverOverlapAbsorber(
|
if (user.canViewTags) _openAddTagPage,
|
||||||
handle: searchBarHandle,
|
if (user.canViewStoragePaths) _openAddStoragePathPage,
|
||||||
sliver: const SliverSearchBar(),
|
][_currentIndex],
|
||||||
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
SliverOverlapAbsorber(
|
body: NestedScrollView(
|
||||||
handle: tabBarHandle,
|
floatHeaderSlivers: true,
|
||||||
sliver: SliverPersistentHeader(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
pinned: true,
|
SliverOverlapAbsorber(
|
||||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
handle: searchBarHandle,
|
||||||
child: ColoredTabBar(
|
sliver: SliverSearchBar(
|
||||||
tabBar: TabBar(
|
titleText: S.of(context)!.labels,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverOverlapAbsorber(
|
||||||
|
handle: tabBarHandle,
|
||||||
|
sliver: SliverPersistentHeader(
|
||||||
|
pinned: true,
|
||||||
|
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||||
|
child: ColoredTabBar(
|
||||||
|
tabBar: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: [
|
||||||
|
if (user.canViewCorrespondents)
|
||||||
|
Tab(
|
||||||
|
icon: Tooltip(
|
||||||
|
message: S.of(context)!.correspondents,
|
||||||
|
child: Icon(
|
||||||
|
Icons.person_outline,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (user.canViewDocumentTypes)
|
||||||
|
Tab(
|
||||||
|
icon: Tooltip(
|
||||||
|
message: S.of(context)!.documentTypes,
|
||||||
|
child: Icon(
|
||||||
|
Icons.description_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (user.canViewTags)
|
||||||
|
Tab(
|
||||||
|
icon: Tooltip(
|
||||||
|
message: S.of(context)!.tags,
|
||||||
|
child: Icon(
|
||||||
|
Icons.label_outline,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (user.canViewStoragePaths)
|
||||||
|
Tab(
|
||||||
|
icon: Tooltip(
|
||||||
|
message: S.of(context)!.storagePaths,
|
||||||
|
child: Icon(
|
||||||
|
Icons.folder_open,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
minExtent: kTextTabBarHeight,
|
||||||
|
maxExtent: kTextTabBarHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
body: BlocBuilder<LabelCubit, LabelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return NotificationListener<ScrollNotification>(
|
||||||
|
onNotification: (notification) {
|
||||||
|
final metrics = notification.metrics;
|
||||||
|
if (metrics.maxScrollExtent == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final desiredTab =
|
||||||
|
((metrics.pixels / metrics.maxScrollExtent) *
|
||||||
|
(_tabController.length - 1))
|
||||||
|
.round();
|
||||||
|
|
||||||
|
if (metrics.axis == Axis.horizontal &&
|
||||||
|
_currentIndex != desiredTab) {
|
||||||
|
setState(() => _currentIndex = desiredTab);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: RefreshIndicator(
|
||||||
|
edgeOffset: kTextTabBarHeight,
|
||||||
|
notificationPredicate: (notification) =>
|
||||||
|
connectedState.isConnected,
|
||||||
|
onRefresh: () async {
|
||||||
|
try {
|
||||||
|
await [
|
||||||
|
context
|
||||||
|
.read<LabelCubit>()
|
||||||
|
.reloadCorrespondents,
|
||||||
|
context
|
||||||
|
.read<LabelCubit>()
|
||||||
|
.reloadDocumentTypes,
|
||||||
|
context.read<LabelCubit>().reloadTags,
|
||||||
|
context.read<LabelCubit>().reloadStoragePaths,
|
||||||
|
][_currentIndex]
|
||||||
|
.call();
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
debugPrint(
|
||||||
|
"[LabelsPage] RefreshIndicator.onRefresh "
|
||||||
|
"${[
|
||||||
|
"correspondents",
|
||||||
|
"document types",
|
||||||
|
"tags",
|
||||||
|
"storage paths"
|
||||||
|
][_currentIndex]}: "
|
||||||
|
"An error occurred (${error.toString()})",
|
||||||
|
);
|
||||||
|
debugPrintStack(stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
tabs: [
|
children: [
|
||||||
Tab(
|
if (user.canViewCorrespondents)
|
||||||
icon: Icon(
|
Builder(
|
||||||
Icons.person_outline,
|
builder: (context) {
|
||||||
color: Theme.of(context)
|
return CustomScrollView(
|
||||||
.colorScheme
|
slivers: [
|
||||||
.onPrimaryContainer,
|
SliverOverlapInjector(
|
||||||
|
handle: searchBarHandle),
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: tabBarHandle),
|
||||||
|
LabelTabView<Correspondent>(
|
||||||
|
labels: state.correspondents,
|
||||||
|
filterBuilder: (label) =>
|
||||||
|
DocumentFilter(
|
||||||
|
correspondent:
|
||||||
|
IdQueryParameter.fromId(
|
||||||
|
label.id!),
|
||||||
|
),
|
||||||
|
canEdit: user.canEditCorrespondents,
|
||||||
|
canAddNew:
|
||||||
|
user.canCreateCorrespondents,
|
||||||
|
onEdit: _openEditCorrespondentPage,
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)!
|
||||||
|
.addNewCorrespondent,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)!
|
||||||
|
.noCorrespondentsSetUp,
|
||||||
|
onAddNew: _openAddCorrespondentPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
if (user.canViewDocumentTypes)
|
||||||
Tab(
|
Builder(
|
||||||
icon: Icon(
|
builder: (context) {
|
||||||
Icons.description_outlined,
|
return CustomScrollView(
|
||||||
color: Theme.of(context)
|
slivers: [
|
||||||
.colorScheme
|
SliverOverlapInjector(
|
||||||
.onPrimaryContainer,
|
handle: searchBarHandle),
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: tabBarHandle),
|
||||||
|
LabelTabView<DocumentType>(
|
||||||
|
labels: state.documentTypes,
|
||||||
|
filterBuilder: (label) =>
|
||||||
|
DocumentFilter(
|
||||||
|
documentType:
|
||||||
|
IdQueryParameter.fromId(
|
||||||
|
label.id!),
|
||||||
|
),
|
||||||
|
canEdit: user.canEditDocumentTypes,
|
||||||
|
canAddNew:
|
||||||
|
user.canCreateDocumentTypes,
|
||||||
|
onEdit: _openEditDocumentTypePage,
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)!
|
||||||
|
.addNewDocumentType,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)!
|
||||||
|
.noDocumentTypesSetUp,
|
||||||
|
onAddNew: _openAddDocumentTypePage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
if (user.canViewTags)
|
||||||
Tab(
|
Builder(
|
||||||
icon: Icon(
|
builder: (context) {
|
||||||
Icons.label_outline,
|
return CustomScrollView(
|
||||||
color: Theme.of(context)
|
slivers: [
|
||||||
.colorScheme
|
SliverOverlapInjector(
|
||||||
.onPrimaryContainer,
|
handle: searchBarHandle),
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: tabBarHandle),
|
||||||
|
LabelTabView<Tag>(
|
||||||
|
labels: state.tags,
|
||||||
|
filterBuilder: (label) =>
|
||||||
|
DocumentFilter(
|
||||||
|
tags: TagsQuery.ids(
|
||||||
|
include: [label.id!]),
|
||||||
|
),
|
||||||
|
canEdit: user.canEditTags,
|
||||||
|
canAddNew: user.canCreateTags,
|
||||||
|
onEdit: _openEditTagPage,
|
||||||
|
leadingBuilder: (t) => CircleAvatar(
|
||||||
|
backgroundColor: t.color,
|
||||||
|
child: t.isInboxTag
|
||||||
|
? Icon(
|
||||||
|
Icons.inbox,
|
||||||
|
color: t.textColor,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
emptyStateActionButtonLabel:
|
||||||
|
S.of(context)!.addNewTag,
|
||||||
|
emptyStateDescription:
|
||||||
|
S.of(context)!.noTagsSetUp,
|
||||||
|
onAddNew: _openAddTagPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
if (user.canViewStoragePaths)
|
||||||
Tab(
|
Builder(
|
||||||
icon: Icon(
|
builder: (context) {
|
||||||
Icons.folder_open,
|
return CustomScrollView(
|
||||||
color: Theme.of(context)
|
slivers: [
|
||||||
.colorScheme
|
SliverOverlapInjector(
|
||||||
.onPrimaryContainer,
|
handle: searchBarHandle),
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: tabBarHandle),
|
||||||
|
LabelTabView<StoragePath>(
|
||||||
|
labels: state.storagePaths,
|
||||||
|
onEdit: _openEditStoragePathPage,
|
||||||
|
filterBuilder: (label) =>
|
||||||
|
DocumentFilter(
|
||||||
|
storagePath:
|
||||||
|
IdQueryParameter.fromId(
|
||||||
|
label.id!),
|
||||||
|
),
|
||||||
|
canEdit: user.canEditStoragePaths,
|
||||||
|
canAddNew:
|
||||||
|
user.canCreateStoragePaths,
|
||||||
|
contentBuilder: (path) =>
|
||||||
|
Text(path.path),
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)!
|
||||||
|
.addNewStoragePath,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)!
|
||||||
|
.noStoragePathsSetUp,
|
||||||
|
onAddNew: _openAddStoragePathPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
minExtent: kTextTabBarHeight,
|
);
|
||||||
maxExtent: kTextTabBarHeight),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
body: BlocBuilder<LabelCubit, LabelState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return NotificationListener<ScrollNotification>(
|
|
||||||
onNotification: (notification) {
|
|
||||||
final metrics = notification.metrics;
|
|
||||||
if (metrics.maxScrollExtent == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final desiredTab =
|
|
||||||
((metrics.pixels / metrics.maxScrollExtent) *
|
|
||||||
(_tabController.length - 1))
|
|
||||||
.round();
|
|
||||||
|
|
||||||
if (metrics.axis == Axis.horizontal &&
|
|
||||||
_currentIndex != desiredTab) {
|
|
||||||
setState(() => _currentIndex = desiredTab);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
child: RefreshIndicator(
|
|
||||||
edgeOffset: kTextTabBarHeight,
|
|
||||||
notificationPredicate: (notification) =>
|
|
||||||
connectedState.isConnected,
|
|
||||||
onRefresh: () async {
|
|
||||||
try {
|
|
||||||
await [
|
|
||||||
context.read<LabelCubit>().reloadCorrespondents,
|
|
||||||
context.read<LabelCubit>().reloadDocumentTypes,
|
|
||||||
context.read<LabelCubit>().reloadTags,
|
|
||||||
context.read<LabelCubit>().reloadStoragePaths,
|
|
||||||
][_currentIndex]
|
|
||||||
.call();
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
debugPrint(
|
|
||||||
"[LabelsPage] RefreshIndicator.onRefresh "
|
|
||||||
"${[
|
|
||||||
"correspondents",
|
|
||||||
"document types",
|
|
||||||
"tags",
|
|
||||||
"storage paths"
|
|
||||||
][_currentIndex]}: "
|
|
||||||
"An error occurred (${error.toString()})",
|
|
||||||
);
|
|
||||||
debugPrintStack(stackTrace: stackTrace);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: [
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: searchBarHandle),
|
|
||||||
SliverOverlapInjector(handle: tabBarHandle),
|
|
||||||
LabelTabView<Correspondent>(
|
|
||||||
labels: state.correspondents,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
correspondent:
|
|
||||||
IdQueryParameter.fromId(label.id!),
|
|
||||||
),
|
|
||||||
canEdit: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.change,
|
|
||||||
PermissionTarget.correspondent),
|
|
||||||
canAddNew: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(PermissionAction.add,
|
|
||||||
PermissionTarget.correspondent),
|
|
||||||
onEdit: _openEditCorrespondentPage,
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context)!.addNewCorrespondent,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context)!.noCorrespondentsSetUp,
|
|
||||||
onAddNew: _openAddCorrespondentPage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: searchBarHandle),
|
|
||||||
SliverOverlapInjector(handle: tabBarHandle),
|
|
||||||
LabelTabView<DocumentType>(
|
|
||||||
labels: state.documentTypes,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
documentType:
|
|
||||||
IdQueryParameter.fromId(label.id!),
|
|
||||||
),
|
|
||||||
canEdit: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.change,
|
|
||||||
PermissionTarget.documentType),
|
|
||||||
canAddNew: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(PermissionAction.add,
|
|
||||||
PermissionTarget.documentType),
|
|
||||||
onEdit: _openEditDocumentTypePage,
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context)!.addNewDocumentType,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context)!.noDocumentTypesSetUp,
|
|
||||||
onAddNew: _openAddDocumentTypePage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: searchBarHandle),
|
|
||||||
SliverOverlapInjector(handle: tabBarHandle),
|
|
||||||
LabelTabView<Tag>(
|
|
||||||
labels: state.tags,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
tags:
|
|
||||||
TagsQuery.ids(include: [label.id!]),
|
|
||||||
),
|
|
||||||
canEdit: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.change,
|
|
||||||
PermissionTarget.tag),
|
|
||||||
canAddNew: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(PermissionAction.add,
|
|
||||||
PermissionTarget.tag),
|
|
||||||
onEdit: _openEditTagPage,
|
|
||||||
leadingBuilder: (t) => CircleAvatar(
|
|
||||||
backgroundColor: t.color,
|
|
||||||
child: t.isInboxTag
|
|
||||||
? Icon(
|
|
||||||
Icons.inbox,
|
|
||||||
color: t.textColor,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context)!.addNewTag,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context)!.noTagsSetUp,
|
|
||||||
onAddNew: _openAddTagPage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: searchBarHandle),
|
|
||||||
SliverOverlapInjector(handle: tabBarHandle),
|
|
||||||
LabelTabView<StoragePath>(
|
|
||||||
labels: state.storagePaths,
|
|
||||||
onEdit: _openEditStoragePathPage,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
storagePath:
|
|
||||||
IdQueryParameter.fromId(label.id!),
|
|
||||||
),
|
|
||||||
canEdit: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(
|
|
||||||
PermissionAction.change,
|
|
||||||
PermissionTarget.storagePath),
|
|
||||||
canAddNew: LocalUserAccount
|
|
||||||
.current.paperlessUser
|
|
||||||
.hasPermission(PermissionAction.add,
|
|
||||||
PermissionTarget.storagePath),
|
|
||||||
contentBuilder: (path) => Text(path.path),
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context)!.addNewStoragePath,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context)!.noStoragePathsSetUp,
|
|
||||||
onAddNew: _openAddStoragePathPage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openEditCorrespondentPage(Correspondent correspondent) {
|
void _openEditCorrespondentPage(Correspondent correspondent) {
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
||||||
final canOpen = (label.documentCount ?? 0) > 0 &&
|
final canOpen = (label.documentCount ?? 0) > 0 &&
|
||||||
LocalUserAccount.current.paperlessUser
|
LocalUserAccount.current.paperlessUser.canViewDocuments;
|
||||||
.hasPermission(PermissionAction.view, PermissionTarget.document);
|
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
label: const Icon(Icons.link),
|
label: const Icon(Icons.link),
|
||||||
icon: Text(formatMaxCount(label.documentCount)),
|
icon: Text(formatMaxCount(label.documentCount)),
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
apiVersion: apiVersion,
|
apiVersion: apiVersion,
|
||||||
)
|
)
|
||||||
.findCurrentUser();
|
.findCurrentUser();
|
||||||
} on DioError catch (error, stackTrace) {
|
} on DioException catch (error, stackTrace) {
|
||||||
_debugPrintMessage(
|
_debugPrintMessage(
|
||||||
"_addUser",
|
"_addUser",
|
||||||
"An error occurred: ${error.message}",
|
"An error occurred: ${error.message}",
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ServerConnectionPage(
|
ServerConnectionPage(
|
||||||
titleString: widget.titleString,
|
titleText: widget.titleString,
|
||||||
formBuilderKey: _formKey,
|
formBuilderKey: _formKey,
|
||||||
onContinue: () {
|
onContinue: () {
|
||||||
_pageController.nextPage(
|
_pageController.nextPage(
|
||||||
@@ -126,7 +126,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _login() async {
|
Future<void> _login() async {
|
||||||
|
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
final form = _formKey.currentState!.value;
|
final form = _formKey.currentState!.value;
|
||||||
@@ -150,7 +149,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
form[ServerAddressFormField.fkServerAddress],
|
form[ServerAddressFormField.fkServerAddress],
|
||||||
clientCert,
|
clientCert,
|
||||||
);
|
);
|
||||||
} on PaperlessServerException catch (error) {
|
} on PaperlessApiException catch (error) {
|
||||||
showErrorMessage(context, error);
|
showErrorMessage(context, error);
|
||||||
} on ServerMessageException catch (error) {
|
} on ServerMessageException catch (error) {
|
||||||
showLocalizedError(context, error.message);
|
showLocalizedError(context, error.message);
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
.values
|
.values
|
||||||
.where((element) => element.contains(textEditingValue.text));
|
.where((element) => element.contains(textEditingValue.text));
|
||||||
},
|
},
|
||||||
onSelected: (option) => _formatInput(),
|
onSelected: (option) {
|
||||||
|
_formatInput();
|
||||||
|
field.didChange(_textEditingController.text);
|
||||||
|
},
|
||||||
fieldViewBuilder:
|
fieldViewBuilder:
|
||||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||||
return TextField(
|
return TextField(
|
||||||
@@ -111,6 +114,10 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
String address = _textEditingController.text.trim();
|
String address = _textEditingController.text.trim();
|
||||||
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||||
_textEditingController.text = address;
|
_textEditingController.text = address;
|
||||||
|
_textEditingController.selection = TextSelection(
|
||||||
|
baseOffset: address.length,
|
||||||
|
extentOffset: address.length,
|
||||||
|
);
|
||||||
widget.onSubmit(address);
|
widget.onSubmit(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
class ServerConnectionPage extends StatefulWidget {
|
class ServerConnectionPage extends StatefulWidget {
|
||||||
final GlobalKey<FormBuilderState> formBuilderKey;
|
final GlobalKey<FormBuilderState> formBuilderKey;
|
||||||
final void Function() onContinue;
|
final VoidCallback onContinue;
|
||||||
final String titleString;
|
final String titleText;
|
||||||
|
|
||||||
const ServerConnectionPage({
|
const ServerConnectionPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.formBuilderKey,
|
required this.formBuilderKey,
|
||||||
required this.onContinue,
|
required this.onContinue,
|
||||||
required this.titleString,
|
required this.titleText,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,7 +36,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
toolbarHeight: kToolbarHeight - 4,
|
toolbarHeight: kToolbarHeight - 4,
|
||||||
title: Text(widget.titleString),
|
title: Text(widget.titleText),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
child: _isCheckingConnection
|
child: _isCheckingConnection
|
||||||
? const LinearProgressIndicator()
|
? const LinearProgressIndicator()
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ mixin DocumentPagingViewMixin<T extends StatefulWidget,
|
|||||||
if (shouldLoadMoreDocuments) {
|
if (shouldLoadMoreDocuments) {
|
||||||
try {
|
try {
|
||||||
await _bloc.loadMore();
|
await _bloc.loadMore();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
super.initState();
|
super.initState();
|
||||||
try {
|
try {
|
||||||
context.read<SimilarDocumentsCubit>().initialize();
|
context.read<SimilarDocumentsCubit>().initialize();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ void showLocalizedError(
|
|||||||
|
|
||||||
void showErrorMessage(
|
void showErrorMessage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
PaperlessServerException error, [
|
PaperlessApiException error, [
|
||||||
StackTrace? stackTrace,
|
StackTrace? stackTrace,
|
||||||
]) {
|
]) {
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import 'package:paperless_mobile/core/interceptor/language_header.interceptor.da
|
|||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/home_route.dart';
|
import 'package:paperless_mobile/features/home/view/home_route.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
|
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
|
||||||
@@ -168,7 +167,7 @@ void main() async {
|
|||||||
);
|
);
|
||||||
}, (error, stack) {
|
}, (error, stack) {
|
||||||
String message = switch (error) {
|
String message = switch (error) {
|
||||||
PaperlessServerException e => e.details ?? error.toString(),
|
PaperlessApiException e => e.details ?? error.toString(),
|
||||||
ServerMessageException e => e.message,
|
ServerMessageException e => e.message,
|
||||||
_ => error.toString()
|
_ => error.toString()
|
||||||
};
|
};
|
||||||
@@ -315,8 +314,10 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await context.read<AuthenticationCubit>().login(
|
await context.read<AuthenticationCubit>().login(
|
||||||
credentials:
|
credentials: LoginFormCredentials(
|
||||||
LoginFormCredentials(username: username, password: password),
|
username: username,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
clientCertificate: clientCertificate,
|
clientCertificate: clientCertificate,
|
||||||
);
|
);
|
||||||
@@ -335,13 +336,17 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
globalSettings.save();
|
globalSettings.save();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessApiException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} on PaperlessValidationErrors catch (error, stackTrace) {
|
} on PaperlessFormValidationException catch (exception, stackTrace) {
|
||||||
if (error.hasFieldUnspecificError) {
|
if (exception.hasUnspecificErrorMessage()) {
|
||||||
showLocalizedError(context, error.fieldUnspecificError!);
|
showLocalizedError(context, exception.unspecificErrorMessage()!);
|
||||||
} else {
|
} else {
|
||||||
showGenericError(context, error.values.first, stackTrace);
|
showGenericError(
|
||||||
|
context,
|
||||||
|
exception.validationMessages.values.first,
|
||||||
|
stackTrace,
|
||||||
|
); //TODO: Check if we can show error message directly on field here.
|
||||||
}
|
}
|
||||||
} catch (unknownError, stackTrace) {
|
} catch (unknownError, stackTrace) {
|
||||||
showGenericError(context, unknownError.toString(), stackTrace);
|
showGenericError(context, unknownError.toString(), stackTrace);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
extension DioExceptionUnravelExtension on DioException {
|
||||||
|
Object unravel({Object? orElse}) {
|
||||||
|
return error ?? orElse ?? Exception("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export 'paperless_server_message_exception.dart';
|
||||||
|
export 'paperless_form_validation_exception.dart';
|
||||||
|
export 'paperless_unauthorized_exception.dart';
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
class PaperlessFormValidationException implements Exception {
|
||||||
|
final Map<String, String> validationMessages;
|
||||||
|
|
||||||
|
PaperlessFormValidationException(this.validationMessages);
|
||||||
|
|
||||||
|
bool hasMessageForField(String formKey) {
|
||||||
|
return validationMessages.containsKey(formKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasUnspecificErrorMessage() {
|
||||||
|
return validationMessages.containsKey("non_field_errors");
|
||||||
|
}
|
||||||
|
|
||||||
|
String? unspecificErrorMessage() {
|
||||||
|
return validationMessages["non_field_errors"];
|
||||||
|
}
|
||||||
|
|
||||||
|
String? messageForField(String formKey) {
|
||||||
|
return validationMessages[formKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canParse(Map<String, dynamic> json) {
|
||||||
|
return json.values.every((element) => element is String);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory PaperlessFormValidationException.fromJson(Map<String, dynamic> json) {
|
||||||
|
final Map<String, String> validationMessages = {};
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
if (entry.value is List) {
|
||||||
|
validationMessages.putIfAbsent(
|
||||||
|
entry.key,
|
||||||
|
() => (entry.value as List).first as String,
|
||||||
|
);
|
||||||
|
} else if (entry.value is String) {
|
||||||
|
validationMessages.putIfAbsent(entry.key, () => entry.value);
|
||||||
|
} else {
|
||||||
|
validationMessages.putIfAbsent(entry.key, () => entry.value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PaperlessFormValidationException(validationMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'paperless_server_exception.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class PaperlessServerMessageException implements Exception {
|
||||||
|
final String detail;
|
||||||
|
|
||||||
|
PaperlessServerMessageException(this.detail);
|
||||||
|
|
||||||
|
static bool canParse(Map<String, dynamic> json) {
|
||||||
|
return json.containsKey('detail') && json.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory PaperlessServerMessageException.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PaperlessServerExceptionFromJson(json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class PaperlessUnauthorizedException implements Exception {
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
PaperlessUnauthorizedException(this.message);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -12,7 +12,7 @@ export 'labels/matching_algorithm.dart';
|
|||||||
export 'labels/storage_path_model.dart';
|
export 'labels/storage_path_model.dart';
|
||||||
export 'labels/tag_model.dart';
|
export 'labels/tag_model.dart';
|
||||||
export 'paged_search_result.dart';
|
export 'paged_search_result.dart';
|
||||||
export 'paperless_server_exception.dart';
|
export 'paperless_api_exception.dart';
|
||||||
export 'paperless_server_information_model.dart';
|
export 'paperless_server_information_model.dart';
|
||||||
export 'paperless_server_statistics_model.dart';
|
export 'paperless_server_statistics_model.dart';
|
||||||
export 'permissions/inherited_permissions.dart';
|
export 'permissions/inherited_permissions.dart';
|
||||||
@@ -31,3 +31,4 @@ export 'saved_view_model.dart';
|
|||||||
export 'task/task.dart';
|
export 'task/task.dart';
|
||||||
export 'task/task_status.dart';
|
export 'task/task_status.dart';
|
||||||
export 'user_model.dart';
|
export 'user_model.dart';
|
||||||
|
export 'exception/exceptions.dart';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
class PaperlessServerException implements Exception {
|
class PaperlessApiException implements Exception {
|
||||||
final ErrorCode code;
|
final ErrorCode code;
|
||||||
final String? details;
|
final String? details;
|
||||||
final StackTrace? stackTrace;
|
final StackTrace? stackTrace;
|
||||||
final int? httpStatusCode;
|
final int? httpStatusCode;
|
||||||
|
|
||||||
const PaperlessServerException(
|
const PaperlessApiException(
|
||||||
this.code, {
|
this.code, {
|
||||||
this.details,
|
this.details,
|
||||||
this.stackTrace,
|
this.stackTrace,
|
||||||
this.httpStatusCode,
|
this.httpStatusCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const PaperlessServerException.unknown() : this(ErrorCode.unknown);
|
const PaperlessApiException.unknown() : this(ErrorCode.unknown);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -53,5 +53,6 @@ enum ErrorCode {
|
|||||||
requestTimedOut,
|
requestTimedOut,
|
||||||
unsupportedFileFormat,
|
unsupportedFileFormat,
|
||||||
missingClientCertificate,
|
missingClientCertificate,
|
||||||
acknowledgeTasksError;
|
acknowledgeTasksError,
|
||||||
|
correspondentDeleteFailed, documentTypeDeleteFailed, tagDeleteFailed, correspondentUpdateFailed, documentTypeUpdateFailed, tagUpdateFailed, storagePathDeleteFailed, storagePathUpdateFailed, serverInformationLoadFailed, serverStatisticsLoadFailed, uiSettingsLoadFailed, loadTasksError, userNotFound;
|
||||||
}
|
}
|
||||||
@@ -6,13 +6,15 @@ extension UserPermissionExtension on UserModel {
|
|||||||
v3: (user) {
|
v3: (user) {
|
||||||
final permission = [action.value, target.value].join("_");
|
final permission = [action.value, target.value].join("_");
|
||||||
return user.userPermissions.any((element) => element == permission) ||
|
return user.userPermissions.any((element) => element == permission) ||
|
||||||
user.inheritedPermissions.any((element) => element.split(".").last == permission);
|
user.inheritedPermissions
|
||||||
|
.any((element) => element.split(".").last == permission);
|
||||||
},
|
},
|
||||||
v2: (_) => true,
|
v2: (_) => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasPermissions(List<PermissionAction> actions, List<PermissionTarget> targets) {
|
bool hasPermissions(
|
||||||
|
List<PermissionAction> actions, List<PermissionTarget> targets) {
|
||||||
return map(
|
return map(
|
||||||
v3: (user) {
|
v3: (user) {
|
||||||
final permissions = [
|
final permissions = [
|
||||||
@@ -21,10 +23,62 @@ extension UserPermissionExtension on UserModel {
|
|||||||
];
|
];
|
||||||
return permissions.every((requestedPermission) =>
|
return permissions.every((requestedPermission) =>
|
||||||
user.userPermissions.contains(requestedPermission) ||
|
user.userPermissions.contains(requestedPermission) ||
|
||||||
user.inheritedPermissions
|
user.inheritedPermissions.any(
|
||||||
.any((element) => element.split(".").last == requestedPermission));
|
(element) => element.split(".").last == requestedPermission));
|
||||||
},
|
},
|
||||||
v2: (_) => true,
|
v2: (_) => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get canViewDocuments =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.document);
|
||||||
|
bool get canViewCorrespondents =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.correspondent);
|
||||||
|
bool get canViewDocumentTypes =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.documentType);
|
||||||
|
bool get canViewTags =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.tag);
|
||||||
|
bool get canViewStoragePaths =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.storagePath);
|
||||||
|
bool get canViewSavedViews =>
|
||||||
|
hasPermission(PermissionAction.view, PermissionTarget.savedView);
|
||||||
|
|
||||||
|
bool get canEditDocuments =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.document);
|
||||||
|
bool get canEditCorrespondents =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.correspondent);
|
||||||
|
bool get canEditDocumentTypes =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.documentType);
|
||||||
|
bool get canEditTags =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.tag);
|
||||||
|
bool get canEditStoragePaths =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.storagePath);
|
||||||
|
bool get canEditavedViews =>
|
||||||
|
hasPermission(PermissionAction.change, PermissionTarget.savedView);
|
||||||
|
|
||||||
|
bool get canDeleteDocuments =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.document);
|
||||||
|
bool get canDeleteCorrespondents =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.correspondent);
|
||||||
|
bool get canDeleteDocumentTypes =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.documentType);
|
||||||
|
bool get canDeleteTags =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.tag);
|
||||||
|
bool get canDeleteStoragePaths =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.storagePath);
|
||||||
|
bool get canDeleteSavedViews =>
|
||||||
|
hasPermission(PermissionAction.delete, PermissionTarget.savedView);
|
||||||
|
|
||||||
|
bool get canCreateDocuments =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.document);
|
||||||
|
bool get canCreateCorrespondents =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.correspondent);
|
||||||
|
bool get canCreateDocumentTypes =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.documentType);
|
||||||
|
bool get canCreateTags =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.tag);
|
||||||
|
bool get canCreateStoragePaths =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.storagePath);
|
||||||
|
bool get canCreateSavedViews =>
|
||||||
|
hasPermission(PermissionAction.add, PermissionTarget.savedView);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
import 'package:paperless_api/src/models/exception/exceptions.dart';
|
||||||
|
|
||||||
abstract class PaperlessAuthenticationApi {
|
abstract class PaperlessAuthenticationApi {
|
||||||
|
///
|
||||||
|
/// @throws [PaperlessUnauthorizedException]
|
||||||
|
///
|
||||||
Future<String> login({
|
Future<String> login({
|
||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
|
||||||
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
||||||
|
|
||||||
class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||||
@@ -13,34 +12,20 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
|||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
late Response response;
|
|
||||||
try {
|
try {
|
||||||
response = await client.post(
|
final response = await client.post(
|
||||||
"/api/token/",
|
"/api/token/",
|
||||||
data: {
|
data: {
|
||||||
"username": username,
|
"username": username,
|
||||||
"password": password,
|
"password": password,
|
||||||
},
|
},
|
||||||
|
options: Options(
|
||||||
|
validateStatus: (status) => status == 200,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (error) {
|
|
||||||
if (error.error is PaperlessServerException ||
|
|
||||||
error.error is Map<String, String>) {
|
|
||||||
throw error.error as Map<String, String>;
|
|
||||||
} else {
|
|
||||||
throw PaperlessServerException(
|
|
||||||
ErrorCode.authenticationFailed,
|
|
||||||
details: error.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.data['token'];
|
return response.data['token'];
|
||||||
} else {
|
} on DioException catch (exception) {
|
||||||
throw PaperlessServerException(
|
throw exception.unravel();
|
||||||
ErrorCode.authenticationFailed,
|
|
||||||
httpStatusCode: response.statusCode,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ abstract class PaperlessDocumentsApi {
|
|||||||
Future<DocumentModel> update(DocumentModel doc);
|
Future<DocumentModel> update(DocumentModel doc);
|
||||||
Future<int> findNextAsn();
|
Future<int> findNextAsn();
|
||||||
Future<PagedSearchResult<DocumentModel>> findAll(DocumentFilter filter);
|
Future<PagedSearchResult<DocumentModel>> findAll(DocumentFilter filter);
|
||||||
Future<DocumentModel?> find(int id);
|
Future<DocumentModel> find(int id);
|
||||||
Future<int> delete(DocumentModel doc);
|
Future<int> delete(DocumentModel doc);
|
||||||
Future<DocumentMetaData> getMetaData(DocumentModel document);
|
Future<DocumentMetaData> getMetaData(DocumentModel document);
|
||||||
Future<Iterable<int>> bulkAction(BulkAction action);
|
Future<Iterable<int>> bulkAction(BulkAction action);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_api/src/constants.dart';
|
import 'package:paperless_api/src/constants.dart';
|
||||||
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
|
|
||||||
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||||
final Dio client;
|
final Dio client;
|
||||||
@@ -55,20 +57,17 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
debugPrint("Uploading ${(count / total) * 100}%...");
|
debugPrint("Uploading ${(count / total) * 100}%...");
|
||||||
},
|
},
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.data != "OK") {
|
||||||
if (response.data is String && response.data != "OK") {
|
return response.data as String;
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
} else {
|
||||||
throw PaperlessServerException(
|
return null;
|
||||||
ErrorCode.documentUploadFailed,
|
|
||||||
httpStatusCode: response.statusCode,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.documentUploadFailed),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,14 +77,13 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
"/api/documents/${doc.id}/",
|
"/api/documents/${doc.id}/",
|
||||||
data: doc.toJson(),
|
data: doc.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
|
return DocumentModel.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.documentUpdateFailed),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return DocumentModel.fromJson(response.data);
|
|
||||||
} else {
|
|
||||||
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
|
||||||
}
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,39 +91,41 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
Future<PagedSearchResult<DocumentModel>> findAll(
|
Future<PagedSearchResult<DocumentModel>> findAll(
|
||||||
DocumentFilter filter,
|
DocumentFilter filter,
|
||||||
) async {
|
) async {
|
||||||
final filterParams = filter.toQueryParameters()..addAll({'truncate_content': "true"});
|
final filterParams = filter.toQueryParameters()
|
||||||
|
..addAll({'truncate_content': "true"});
|
||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
"/api/documents/",
|
"/api/documents/",
|
||||||
queryParameters: filterParams,
|
queryParameters: filterParams,
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
|
return compute(
|
||||||
|
PagedSearchResult.fromJsonSingleParam,
|
||||||
|
PagedSearchResultJsonSerializer<DocumentModel>(
|
||||||
|
response.data,
|
||||||
|
DocumentModelJsonConverter(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.documentLoadFailed),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return compute(
|
|
||||||
PagedSearchResult.fromJsonSingleParam,
|
|
||||||
PagedSearchResultJsonSerializer<DocumentModel>(
|
|
||||||
response.data,
|
|
||||||
DocumentModelJsonConverter(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw const PaperlessServerException(ErrorCode.documentLoadFailed);
|
|
||||||
}
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(DocumentModel doc) async {
|
Future<int> delete(DocumentModel doc) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.delete("/api/documents/${doc.id}/");
|
await client.delete(
|
||||||
|
"/api/documents/${doc.id}/",
|
||||||
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusCode == 204) {
|
return Future.value(doc.id);
|
||||||
return Future.value(doc.id);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw const PaperlessServerException(ErrorCode.documentDeleteFailed);
|
orElse: const PaperlessApiException(ErrorCode.documentDeleteFailed),
|
||||||
} on DioError catch (err) {
|
);
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,15 +143,16 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
getPreviewUrl(documentId),
|
getPreviewUrl(documentId),
|
||||||
options:
|
options: Options(
|
||||||
Options(responseType: ResponseType.bytes), //TODO: Check if bytes or stream is required
|
responseType: ResponseType.bytes,
|
||||||
|
validateStatus: (status) => status == 200,
|
||||||
|
), //TODO: Check if bytes or stream is required
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.documentPreviewFailed),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,29 +171,30 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
.map((e) => e.archiveSerialNumber)
|
.map((e) => e.archiveSerialNumber)
|
||||||
.firstWhere((asn) => asn != null, orElse: () => 0)! +
|
.firstWhere((asn) => asn != null, orElse: () => 0)! +
|
||||||
1;
|
1;
|
||||||
} on PaperlessServerException {
|
} on PaperlessApiException {
|
||||||
throw const PaperlessServerException(ErrorCode.documentAsnQueryFailed);
|
throw const PaperlessApiException(ErrorCode.documentAsnQueryFailed);
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.documentAsnQueryFailed),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.post(
|
await client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
data: action.toJson(),
|
data: action.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
return action.documentIds;
|
||||||
return action.documentIds;
|
} on DioException catch (exception) {
|
||||||
} else {
|
throw exception.unravel(
|
||||||
throw const PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.documentBulkActionFailed,
|
ErrorCode.documentBulkActionFailed,
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +210,10 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
options: Options(responseType: ResponseType.bytes),
|
options: Options(responseType: ResponseType.bytes),
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException.unknown(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,25 +228,31 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
final response = await client.download(
|
final response = await client.download(
|
||||||
"/api/documents/${document.id}/download/",
|
"/api/documents/${document.id}/download/",
|
||||||
localFilePath,
|
localFilePath,
|
||||||
onReceiveProgress: (count, total) => onProgressChanged?.call(count / total),
|
onReceiveProgress: (count, total) =>
|
||||||
|
onProgressChanged?.call(count / total),
|
||||||
queryParameters: {'original': original},
|
queryParameters: {'original': original},
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException.unknown(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get("/api/documents/${document.id}/metadata/");
|
final response =
|
||||||
|
await client.get("/api/documents/${document.id}/metadata/");
|
||||||
return compute(
|
return compute(
|
||||||
DocumentMetaData.fromJson,
|
DocumentMetaData.fromJson,
|
||||||
response.data as Map<String, dynamic>,
|
response.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException.unknown(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,40 +265,46 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
'term': query,
|
'term': query,
|
||||||
'limit': limit,
|
'limit': limit,
|
||||||
},
|
},
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
|
return (response.data as List).cast<String>();
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.autocompleteQueryError,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return (response.data as List).cast<String>();
|
|
||||||
}
|
|
||||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldSuggestions> findSuggestions(DocumentModel document) async {
|
Future<FieldSuggestions> findSuggestions(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get("/api/documents/${document.id}/suggestions/");
|
final response = await client.get(
|
||||||
if (response.statusCode == 200) {
|
"/api/documents/${document.id}/suggestions/",
|
||||||
return FieldSuggestions.fromJson(response.data).forDocumentId(document.id);
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
}
|
);
|
||||||
throw const PaperlessServerException(ErrorCode.suggestionsQueryError);
|
return FieldSuggestions.fromJson(response.data)
|
||||||
} on DioError catch (err) {
|
.forDocumentId(document.id);
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.suggestionsQueryError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentModel?> find(int id) async {
|
Future<DocumentModel> find(int id) async {
|
||||||
try {
|
try {
|
||||||
final response = await client.get("/api/documents/$id/");
|
final response = await client.get(
|
||||||
if (response.statusCode == 200) {
|
"/api/documents/$id/",
|
||||||
return DocumentModel.fromJson(response.data);
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
} else {
|
);
|
||||||
return null;
|
return DocumentModel.fromJson(response.data);
|
||||||
}
|
} on DioException catch (exception) {
|
||||||
} on DioError catch (err) {
|
throw exception.unravel(
|
||||||
throw err.error ?? const PaperlessServerException.unknown();
|
orElse: const PaperlessApiException.unknown(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
||||||
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
||||||
import 'package:paperless_api/src/request_utils.dart';
|
import 'package:paperless_api/src/request_utils.dart';
|
||||||
|
|
||||||
@@ -94,16 +95,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/api/correspondents/',
|
'/api/correspondents/',
|
||||||
data: correspondent.toJson(),
|
data: correspondent.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 201),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
return Correspondent.fromJson(response.data);
|
||||||
return Correspondent.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.correspondentCreateFailed,
|
ErrorCode.correspondentCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,16 +113,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/api/document_types/',
|
'/api/document_types/',
|
||||||
data: type.toJson(),
|
data: type.toJson(),
|
||||||
|
options: Options(
|
||||||
|
validateStatus: (status) => status == 201,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
return DocumentType.fromJson(response.data);
|
||||||
return DocumentType.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.documentTypeCreateFailed,
|
ErrorCode.documentTypeCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,17 +133,18 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/api/tags/',
|
'/api/tags/',
|
||||||
data: tag.toJson(),
|
data: tag.toJson(),
|
||||||
options: Options(headers: {"Accept": "application/json; version=2"}),
|
options: Options(
|
||||||
|
headers: {"Accept": "application/json; version=2"},
|
||||||
|
validateStatus: (status) => status == 201,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
return Tag.fromJson(response.data);
|
||||||
return Tag.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.tagCreateFailed,
|
ErrorCode.tagCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,17 +152,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
||||||
assert(correspondent.id != null);
|
assert(correspondent.id != null);
|
||||||
try {
|
try {
|
||||||
final response =
|
await _client.delete(
|
||||||
await _client.delete('/api/correspondents/${correspondent.id}/');
|
'/api/correspondents/${correspondent.id}/',
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
return correspondent.id!;
|
);
|
||||||
}
|
return correspondent.id!;
|
||||||
throw PaperlessServerException(
|
} on DioException catch (exception) {
|
||||||
ErrorCode.unknown,
|
throw exception.unravel(
|
||||||
httpStatusCode: response.statusCode,
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.correspondentDeleteFailed,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,17 +170,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<int> deleteDocumentType(DocumentType documentType) async {
|
Future<int> deleteDocumentType(DocumentType documentType) async {
|
||||||
assert(documentType.id != null);
|
assert(documentType.id != null);
|
||||||
try {
|
try {
|
||||||
final response =
|
final response = await _client.delete(
|
||||||
await _client.delete('/api/document_types/${documentType.id}/');
|
'/api/document_types/${documentType.id}/',
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
return documentType.id!;
|
);
|
||||||
}
|
return documentType.id!;
|
||||||
throw PaperlessServerException(
|
} on DioException catch (exception) {
|
||||||
ErrorCode.unknown,
|
throw exception.unravel(
|
||||||
httpStatusCode: response.statusCode,
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.documentTypeDeleteFailed,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,16 +188,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<int> deleteTag(Tag tag) async {
|
Future<int> deleteTag(Tag tag) async {
|
||||||
assert(tag.id != null);
|
assert(tag.id != null);
|
||||||
try {
|
try {
|
||||||
final response = await _client.delete('/api/tags/${tag.id}/');
|
await _client.delete(
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
'/api/tags/${tag.id}/',
|
||||||
return tag.id!;
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
}
|
);
|
||||||
throw PaperlessServerException(
|
return tag.id!;
|
||||||
ErrorCode.unknown,
|
} on DioException catch (exception) {
|
||||||
httpStatusCode: response.statusCode,
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.tagDeleteFailed,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,16 +209,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'/api/correspondents/${correspondent.id}/',
|
'/api/correspondents/${correspondent.id}/',
|
||||||
data: json.encode(correspondent.toJson()),
|
data: json.encode(correspondent.toJson()),
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
return Correspondent.fromJson(response.data);
|
||||||
return Correspondent.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.unknown, //TODO: Add correct error code mapping.
|
ErrorCode.correspondentUpdateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,16 +228,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'/api/document_types/${documentType.id}/',
|
'/api/document_types/${documentType.id}/',
|
||||||
data: documentType.toJson(),
|
data: documentType.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
return DocumentType.fromJson(response.data);
|
||||||
return DocumentType.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.unknown,
|
ErrorCode.documentTypeUpdateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,18 +246,19 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
try {
|
try {
|
||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'/api/tags/${tag.id}/',
|
'/api/tags/${tag.id}/',
|
||||||
options: Options(headers: {"Accept": "application/json; version=2"}),
|
options: Options(
|
||||||
|
headers: {"Accept": "application/json; version=2"},
|
||||||
|
validateStatus: (status) => status == 200,
|
||||||
|
),
|
||||||
data: tag.toJson(),
|
data: tag.toJson(),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
return Tag.fromJson(response.data);
|
||||||
return Tag.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.unknown,
|
ErrorCode.tagUpdateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,16 +266,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
Future<int> deleteStoragePath(StoragePath path) async {
|
Future<int> deleteStoragePath(StoragePath path) async {
|
||||||
assert(path.id != null);
|
assert(path.id != null);
|
||||||
try {
|
try {
|
||||||
final response = await _client.delete('/api/storage_paths/${path.id}/');
|
final response = await _client.delete(
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
'/api/storage_paths/${path.id}/',
|
||||||
return path.id!;
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
}
|
);
|
||||||
throw PaperlessServerException(
|
return path.id!;
|
||||||
ErrorCode.unknown,
|
} on DioException catch (exception) {
|
||||||
httpStatusCode: response.statusCode,
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.storagePathDeleteFailed,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,16 +310,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/api/storage_paths/',
|
'/api/storage_paths/',
|
||||||
data: path.toJson(),
|
data: path.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 201),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
return StoragePath.fromJson(response.data);
|
||||||
return StoragePath.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(
|
||||||
ErrorCode.storagePathCreateFailed,
|
ErrorCode.storagePathCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,13 +329,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'/api/storage_paths/${path.id}/',
|
'/api/storage_paths/${path.id}/',
|
||||||
data: path.toJson(),
|
data: path.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
|
return StoragePath.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.storagePathUpdateFailed,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
|
||||||
return StoragePath.fromJson(response.data);
|
|
||||||
}
|
|
||||||
throw const PaperlessServerException(ErrorCode.unknown);
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
import 'package:paperless_api/src/models/saved_view_model.dart';
|
import 'package:paperless_api/src/models/saved_view_model.dart';
|
||||||
import 'package:paperless_api/src/request_utils.dart';
|
import 'package:paperless_api/src/request_utils.dart';
|
||||||
|
|
||||||
@@ -30,32 +31,28 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
"/api/saved_views/",
|
"/api/saved_views/",
|
||||||
data: view.toJson(),
|
data: view.toJson(),
|
||||||
|
options: Options(validateStatus: (status) => status == 201),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
return SavedView.fromJson(response.data);
|
||||||
return SavedView.fromJson(response.data);
|
} on DioException catch (exception) {
|
||||||
}
|
throw exception.unravel(
|
||||||
throw PaperlessServerException(
|
orElse: const PaperlessApiException(ErrorCode.createSavedViewError),
|
||||||
ErrorCode.createSavedViewError,
|
|
||||||
httpStatusCode: response.statusCode,
|
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(SavedView view) async {
|
Future<int> delete(SavedView view) async {
|
||||||
try {
|
try {
|
||||||
final response = await _client.delete("/api/saved_views/${view.id}/");
|
await _client.delete(
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
"/api/saved_views/${view.id}/",
|
||||||
return view.id!;
|
options: Options(validateStatus: (status) => status == 204),
|
||||||
}
|
);
|
||||||
throw PaperlessServerException(
|
return view.id!;
|
||||||
ErrorCode.deleteSavedViewError,
|
} on DioException catch (exception) {
|
||||||
httpStatusCode: response.statusCode,
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.deleteSavedViewError),
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_information_model.dart';
|
import 'package:paperless_api/src/models/paperless_server_information_model.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_statistics_model.dart';
|
import 'package:paperless_api/src/models/paperless_server_statistics_model.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_ui_settings_model.dart';
|
import 'package:paperless_api/src/models/paperless_ui_settings_model.dart';
|
||||||
@@ -18,8 +19,11 @@ class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaperlessServerInformationModel> getServerInformation() async {
|
Future<PaperlessServerInformationModel> getServerInformation() async {
|
||||||
final response = await client.get("/api/remote_version/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await client.get(
|
||||||
|
"/api/remote_version/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
final version = response.data["version"] as String;
|
final version = response.data["version"] as String;
|
||||||
final updateAvailable = response.data["update_available"] as bool;
|
final updateAvailable = response.data["update_available"] as bool;
|
||||||
return PaperlessServerInformationModel(
|
return PaperlessServerInformationModel(
|
||||||
@@ -27,25 +31,44 @@ class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
|
|||||||
version: version,
|
version: version,
|
||||||
isUpdateAvailable: updateAvailable,
|
isUpdateAvailable: updateAvailable,
|
||||||
);
|
);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.serverInformationLoadFailed,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaperlessServerStatisticsModel> getServerStatistics() async {
|
Future<PaperlessServerStatisticsModel> getServerStatistics() async {
|
||||||
final response = await client.get('/api/statistics/');
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await client.get(
|
||||||
|
'/api/statistics/',
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return PaperlessServerStatisticsModel.fromJson(response.data);
|
return PaperlessServerStatisticsModel.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(
|
||||||
|
ErrorCode.serverStatisticsLoadFailed,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaperlessUiSettingsModel> getUiSettings() async {
|
Future<PaperlessUiSettingsModel> getUiSettings() async {
|
||||||
final response = await client.get("/api/ui_settings/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await client.get(
|
||||||
|
"/api/ui_settings/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return PaperlessUiSettingsModel.fromJson(response.data);
|
return PaperlessUiSettingsModel.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.uiSettingsLoadFailed),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'dart:developer';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
|
|
||||||
class PaperlessTasksApiImpl implements PaperlessTasksApi {
|
class PaperlessTasksApiImpl implements PaperlessTasksApi {
|
||||||
final Dio _client;
|
final Dio _client;
|
||||||
@@ -41,11 +43,17 @@ class PaperlessTasksApiImpl implements PaperlessTasksApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<Task>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<Task>> findAll([Iterable<int>? ids]) async {
|
||||||
final response = await _client.get("/api/tasks/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await _client.get(
|
||||||
|
"/api/tasks/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return (response.data as List).map((e) => Task.fromJson(e));
|
return (response.data as List).map((e) => Task.fromJson(e));
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.loadTasksError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -74,15 +82,22 @@ class PaperlessTasksApiImpl implements PaperlessTasksApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<Task>> acknowledgeTasks(Iterable<Task> tasks) async {
|
Future<Iterable<Task>> acknowledgeTasks(Iterable<Task> tasks) async {
|
||||||
final response = await _client.post("/api/acknowledge_tasks/", data: {
|
try {
|
||||||
'tasks': tasks.map((e) => e.id).toList(),
|
final response = await _client.post(
|
||||||
});
|
"/api/acknowledge_tasks/",
|
||||||
if (response.statusCode == 200) {
|
data: {
|
||||||
|
'tasks': tasks.map((e) => e.id).toList(),
|
||||||
|
},
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
if (response.data['result'] != tasks.length) {
|
if (response.data['result'] != tasks.length) {
|
||||||
throw const PaperlessServerException(ErrorCode.acknowledgeTasksError);
|
throw const PaperlessApiException(ErrorCode.acknowledgeTasksError);
|
||||||
}
|
}
|
||||||
return tasks.map((e) => e.copyWith(acknowledged: true)).toList();
|
return tasks.map((e) => e.copyWith(acknowledged: true)).toList();
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.acknowledgeTasksError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.acknowledgeTasksError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
|
|
||||||
class PaperlessUserApiV2Impl implements PaperlessUserApi {
|
class PaperlessUserApiV2Impl implements PaperlessUserApi {
|
||||||
final Dio client;
|
final Dio client;
|
||||||
@@ -8,19 +10,33 @@ class PaperlessUserApiV2Impl implements PaperlessUserApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> findCurrentUserId() async {
|
Future<int> findCurrentUserId() async {
|
||||||
final response = await client.get("/api/ui_settings/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await client.get(
|
||||||
|
"/api/ui_settings/",
|
||||||
|
options: Options(
|
||||||
|
validateStatus: (status) => status == 200,
|
||||||
|
),
|
||||||
|
);
|
||||||
return response.data['user_id'];
|
return response.data['user_id'];
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<UserModel> findCurrentUser() async {
|
Future<UserModel> findCurrentUser() async {
|
||||||
final response = await client.get("/api/ui_settings/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await client.get(
|
||||||
|
"/api/ui_settings/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return UserModelV2.fromJson(response.data);
|
return UserModelV2.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
|
|
||||||
class PaperlessUserApiV3Impl implements PaperlessUserApi, PaperlessUserApiV3 {
|
class PaperlessUserApiV3Impl implements PaperlessUserApi, PaperlessUserApiV3 {
|
||||||
final Dio dio;
|
final Dio dio;
|
||||||
@@ -8,11 +10,17 @@ class PaperlessUserApiV3Impl implements PaperlessUserApi, PaperlessUserApiV3 {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<UserModelV3> find(int id) async {
|
Future<UserModelV3> find(int id) async {
|
||||||
final response = await dio.get("/api/users/$id/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await dio.get(
|
||||||
|
"/api/users/$id/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return UserModelV3.fromJson(response.data);
|
return UserModelV3.fromJson(response.data);
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -22,40 +30,59 @@ class PaperlessUserApiV3Impl implements PaperlessUserApi, PaperlessUserApiV3 {
|
|||||||
String contains = '',
|
String contains = '',
|
||||||
String username = '',
|
String username = '',
|
||||||
}) async {
|
}) async {
|
||||||
final response = await dio.get("/api/users/", queryParameters: {
|
try {
|
||||||
"username__istartswith": startsWith,
|
final response = await dio.get(
|
||||||
"username__iendswith": endsWith,
|
"/api/users/",
|
||||||
"username__icontains": contains,
|
queryParameters: {
|
||||||
"username__iexact": username,
|
"username__istartswith": startsWith,
|
||||||
});
|
"username__iendswith": endsWith,
|
||||||
if (response.statusCode == 200) {
|
"username__icontains": contains,
|
||||||
|
"username__iexact": username,
|
||||||
|
},
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return PagedSearchResult<UserModelV3>.fromJson(
|
return PagedSearchResult<UserModelV3>.fromJson(
|
||||||
response.data,
|
response.data,
|
||||||
UserModelV3.fromJson as UserModelV3 Function(Object?),
|
UserModelV3.fromJson as UserModelV3 Function(Object?),
|
||||||
).results;
|
).results;
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> findCurrentUserId() async {
|
Future<int> findCurrentUserId() async {
|
||||||
final response = await dio.get("/api/ui_settings/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await dio.get(
|
||||||
|
"/api/ui_settings/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return response.data['user']['id'];
|
return response.data['user']['id'];
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<UserModelV3>> findAll() async {
|
Future<Iterable<UserModelV3>> findAll() async {
|
||||||
final response = await dio.get("/api/users/");
|
try {
|
||||||
if (response.statusCode == 200) {
|
final response = await dio.get(
|
||||||
|
"/api/users/",
|
||||||
|
options: Options(validateStatus: (status) => status == 200),
|
||||||
|
);
|
||||||
return PagedSearchResult<UserModelV3>.fromJson(
|
return PagedSearchResult<UserModelV3>.fromJson(
|
||||||
response.data,
|
response.data,
|
||||||
(json) => UserModelV3.fromJson(json as dynamic),
|
(json) => UserModelV3.fromJson(json as dynamic),
|
||||||
).results;
|
).results;
|
||||||
|
} on DioException catch (exception) {
|
||||||
|
throw exception.unravel(
|
||||||
|
orElse: const PaperlessApiException(ErrorCode.userNotFound),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
|
|
||||||
Future<T?> getSingleResult<T>(
|
Future<T?> getSingleResult<T>(
|
||||||
String url,
|
String url,
|
||||||
@@ -16,20 +17,15 @@ Future<T?> getSingleResult<T>(
|
|||||||
url,
|
url,
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||||
|
validateStatus: (status) => status == 200,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
return compute(
|
||||||
return compute(
|
fromJson,
|
||||||
fromJson,
|
response.data as Map<String, dynamic>,
|
||||||
response.data as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw PaperlessServerException(
|
|
||||||
errorCode,
|
|
||||||
httpStatusCode: response.statusCode,
|
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioException catch (exception) {
|
||||||
throw err.error!;
|
throw exception.unravel(orElse: PaperlessApiException(errorCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,30 +39,25 @@ Future<List<T>> getCollection<T>(
|
|||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
url,
|
url,
|
||||||
options: Options(headers: {
|
options: Options(
|
||||||
'accept': 'application/json; version=$minRequiredApiVersion'
|
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||||
}),
|
validateStatus: (status) => status == 200,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
final Map<String, dynamic> body = response.data;
|
||||||
final Map<String, dynamic> body = response.data;
|
if (body['count'] == 0) {
|
||||||
if (body.containsKey('count')) {
|
return <T>[];
|
||||||
if (body['count'] == 0) {
|
} else {
|
||||||
return <T>[];
|
return compute(
|
||||||
} else {
|
_collectionFromJson,
|
||||||
return compute(
|
_CollectionFromJsonSerializationParams(
|
||||||
_collectionFromJson,
|
fromJson,
|
||||||
_CollectionFromJsonSerializationParams(fromJson,
|
(body['results'] as List).cast<Map<String, dynamic>>(),
|
||||||
(body['results'] as List).cast<Map<String, dynamic>>()),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(
|
} on DioException catch (exception) {
|
||||||
errorCode,
|
throw exception.unravel(orElse: PaperlessApiException(errorCode));
|
||||||
httpStatusCode: response.statusCode,
|
|
||||||
);
|
|
||||||
} on DioError catch (err) {
|
|
||||||
throw err.error!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,42 +29,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: camera
|
name: camera
|
||||||
sha256: "309b823e61f15ff6b5b2e4c0ff2e1512ea661cad5355f71fc581e510ae5b26bb"
|
sha256: ebebead3d5ec3d148249331d751d462d7e8c98102b8830a9b45ec96a2bd4333f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.5"
|
version: "0.10.5+2"
|
||||||
camera_android:
|
camera_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_android
|
name: camera_android
|
||||||
sha256: e0f9b7eea2d1f4d4f5460f178522f0d02c095d2ae00b01a77419ce61c4184bfe
|
sha256: f43d07f9d7228ea1ca87d22e30881bd68da4b78484a1fbd1f1408b412a41cefb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.7"
|
version: "0.10.8+3"
|
||||||
camera_avfoundation:
|
camera_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_avfoundation
|
name: camera_avfoundation
|
||||||
sha256: "7ac8b950672716722af235eed7a7c37896853669800b7da706bb0a9fd41d3737"
|
sha256: "1a416e452b30955b392f4efbf23291d3f2ba3660a85e1628859eb62d2a2bab26"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.13+1"
|
version: "0.9.13+2"
|
||||||
camera_platform_interface:
|
camera_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_platform_interface
|
name: camera_platform_interface
|
||||||
sha256: "525017018d116c5db8c4c43ec2d9b1663216b369c9f75149158280168a7ce472"
|
sha256: "60fa0bb62a4f3bf3a7c413e31e4cd01b69c779ccc8e4668904a24581b86c316b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.1"
|
||||||
camera_web:
|
camera_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_web
|
name: camera_web
|
||||||
sha256: d77965f32479ee6d8f48205dcf10f845d7210595c6c00faa51eab265d1cae993
|
sha256: bcbd775fb3a9d51cc3ece899d54ad66f6306410556bac5759f78e13f9228841f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1+3"
|
version: "0.3.1+4"
|
||||||
camerawesome:
|
camerawesome:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -178,18 +178,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
|
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0"
|
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.14"
|
version: "2.0.15"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -220,10 +220,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015"
|
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -299,10 +299,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1"
|
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.10"
|
version: "2.1.11"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -315,10 +315,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
|
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.1.7"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -456,10 +456,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.4"
|
version: "5.0.5"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -477,5 +477,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0-417 <4.0.0"
|
dart: ">=3.0.0 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.3.0"
|
||||||
|
|||||||
136
pubspec.lock
136
pubspec.lock
@@ -101,10 +101,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: bidi
|
name: bidi
|
||||||
sha256: dc00274c7edabae2ab30c676e736ea1eb0b1b7a1b436cb5fe372e431ccb39ab0
|
sha256: "6794b226bc939731308b8539c49bb6c2fdbf0e78c3a65e9b9e81e727c256dfe6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.0.7"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -133,10 +133,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc"
|
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.1"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -157,18 +157,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
|
sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.1"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "5e1929ad37d48bd382b124266cb8e521de5548d406a45a5ae6656c13dab73e37"
|
sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "2.4.6"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -333,10 +333,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad
|
sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -389,27 +389,27 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dots_indicator
|
name: dots_indicator
|
||||||
sha256: f1599baa429936ba87f06ae5f2adc920a367b16d08f74db58c3d0f6e93bcdb5c
|
sha256: "58b6a365744aa62aa1b70c4ea29e5106fbe064f5edaf7e9652e9b856edbfd9bb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "3.0.0"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_color
|
name: dynamic_color
|
||||||
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
|
sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.5"
|
version: "1.6.6"
|
||||||
edge_detection:
|
edge_detection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: master
|
ref: master
|
||||||
resolved-ref: "6ca5e015fc9cb4603890bddacdea0cafb839650d"
|
resolved-ref: "01636d9050d409177934ec64876c1c83c2567513"
|
||||||
url: "https://github.com/sawankumarbundelkhandi/edge_detection"
|
url: "https://github.com/sawankumarbundelkhandi/edge_detection"
|
||||||
source: git
|
source: git
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -483,10 +483,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_cache_manager
|
name: flutter_cache_manager
|
||||||
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3"
|
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.3.1"
|
||||||
flutter_colorpicker:
|
flutter_colorpicker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -698,10 +698,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_typeahead
|
name: flutter_typeahead
|
||||||
sha256: d72e7079d01b4ec109a12b01b06a85c09a41ae4531f8a0ca5ef9f759ce4e64a2
|
sha256: a3539f7a90246b152f569029dedcf0b842532d3f2a440701b520e0bf2acbcf42
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.1"
|
version: "4.6.2"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -719,10 +719,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: font_awesome_flutter
|
name: font_awesome_flutter
|
||||||
sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206"
|
sha256: "5fb789145cae1f4c3245c58b3f8fb287d055c26323879eab57a7bf0cfd1e45f3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.4.0"
|
version: "10.5.0"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -764,10 +764,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: graphs
|
name: graphs
|
||||||
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.1"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -804,10 +804,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
|
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
http_methods:
|
http_methods:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -836,10 +836,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hydrated_bloc
|
name: hydrated_bloc
|
||||||
sha256: "0ea117b32259d9a79c2a2d33eef92e9dd676b88ec4f1ef94102c5889ca1673b6"
|
sha256: "24994e61f64904d911683cce1a31dc4ef611619da5253f1de2b7b8fc6f79a118"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.1"
|
version: "9.1.2"
|
||||||
image:
|
image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -865,10 +865,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: introduction_screen
|
name: introduction_screen
|
||||||
sha256: f194ae655a84b945a2aedb7961d09948d789fc91088efb032666112923bcbc1e
|
sha256: f39be426026785b8fea4ed93e226e7fc28ef49a4c78c3f86c958bae26dabef00
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.8"
|
version: "3.1.9"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -905,10 +905,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: "61a60716544392a82726dd0fa1dd6f5f1fd32aec66422b6e229e7b90d52325c4"
|
sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.0"
|
version: "6.7.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -961,10 +961,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_windows
|
name: local_auth_windows
|
||||||
sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73"
|
sha256: "5af808e108c445d0cf702a8c5f8242f1363b7970320334f82e6e1e8ad0b0d7d4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.9"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1179,54 +1179,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.4"
|
version: "3.10.4"
|
||||||
pedantic:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: pedantic
|
|
||||||
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.11.1"
|
|
||||||
permission_handler:
|
permission_handler:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331"
|
sha256: "415af30ba76a84faccfe1eb251fe1e4fdc790f876924c65ad7d6ed7a1404bcd6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.0"
|
version: "10.4.2"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7"
|
sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.2.3"
|
version: "10.3.0"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb"
|
sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.1.3"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11
|
sha256: "463a07cb7cc6c758a7a1c7da36ce666bb80a0b4b5e92df0fa36872e0ed456993"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.0"
|
version: "3.11.1"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2"
|
version: "0.1.3"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1327,10 +1319,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_updater
|
name: pub_updater
|
||||||
sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c"
|
sha256: b06600619c8c219065a548f8f7c192b3e080beff95488ed692780f48f69c0625
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.1"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1484,18 +1476,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33"
|
sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.4.0"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.4"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1652,18 +1644,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
|
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.11"
|
version: "6.1.12"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
|
sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.35"
|
version: "6.0.36"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1692,26 +1684,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
|
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.17"
|
version: "2.0.18"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
|
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.7"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1780,10 +1772,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: "57a22c86065375c1598b57224f92d6008141be0c877c64100de8bfb6f71083d8"
|
sha256: "1c93e96f3069bacdc734fad6b7e1d3a480fd516a3ae5b8858becf7f07515a2f3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.7.1"
|
version: "3.8.2"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1796,10 +1788,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: "6bbc6ade302b842999b27cbaa7171241c273deea8a9c73f92ceb3d811c767de2"
|
sha256: a8d7e8b4be2a79e83b70235369971ec97d14df4cdbb40d305a8eeae67d8e6432
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.4"
|
version: "3.6.2"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import 'dart:convert';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
|
||||||
|
|
||||||
Future<T> loadOne<T>(String filePath, T Function(JSON) transformFn, int? id) async {
|
Future<T> loadOne<T>(String filePath,
|
||||||
|
T Function(Map<String, dynamic>) transformFn, int? id) async {
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
final coll = await loadCollection(filePath, transformFn);
|
final coll = await loadCollection(filePath, transformFn);
|
||||||
return coll.firstWhere((dynamic element) => element.id == id);
|
return coll.firstWhere((dynamic element) => element.id == id);
|
||||||
@@ -13,22 +13,27 @@ Future<T> loadOne<T>(String filePath, T Function(JSON) transformFn, int? id) asy
|
|||||||
return transformFn(jsonDecode(response));
|
return transformFn(jsonDecode(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<T>> loadCollection<T>(String filePath, T Function(JSON) transformFn,
|
Future<List<T>> loadCollection<T>(
|
||||||
|
String filePath, T Function(Map<String, dynamic>) transformFn,
|
||||||
{int? numItems, List<int>? ids}) async {
|
{int? numItems, List<int>? ids}) async {
|
||||||
assert(((numItems != null) ^ (ids != null)) || (numItems == null && ids == null));
|
assert(((numItems != null) ^ (ids != null)) ||
|
||||||
|
(numItems == null && ids == null));
|
||||||
final String response = await rootBundle.loadString(filePath);
|
final String response = await rootBundle.loadString(filePath);
|
||||||
final lst = (jsonDecode(response) as List<dynamic>);
|
final lst = (jsonDecode(response) as List<dynamic>);
|
||||||
final res = (jsonDecode(response) as List<dynamic>).map((e) => transformFn(e)).toList();
|
final res = (jsonDecode(response) as List<dynamic>)
|
||||||
|
.map((e) => transformFn(e))
|
||||||
|
.toList();
|
||||||
if (ids != null) {
|
if (ids != null) {
|
||||||
return res.where((dynamic element) => ids.contains(element.id)).toList();
|
return res.where((dynamic element) => ids.contains(element.id)).toList();
|
||||||
}
|
}
|
||||||
if (numItems != null && lst.length < numItems) {
|
if (numItems != null && lst.length < numItems) {
|
||||||
throw Exception("The requested collection contains only ${lst.length} items!");
|
throw Exception(
|
||||||
|
"The requested collection contains only ${lst.length} items!");
|
||||||
} else {
|
} else {
|
||||||
return res.sublist(0, numItems);
|
return res.sublist(0, numItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
|
const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
|
||||||
String getRandomString(int length) => String.fromCharCodes(
|
String getRandomString(int length) => String.fromCharCodes(Iterable.generate(
|
||||||
Iterable.generate(length, (_) => _chars.codeUnitAt(Random().nextInt(_chars.length))));
|
length, (_) => _chars.codeUnitAt(Random().nextInt(_chars.length))));
|
||||||
|
|||||||
Reference in New Issue
Block a user