mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 14:07:49 -06:00
WIP - Redesigned login flow
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||||
|
|
||||||
class ConnectivityCubit extends Cubit<ConnectivityState> {
|
class ConnectivityCubit extends Cubit<ConnectivityState> {
|
||||||
final ConnectivityStatusService connectivityStatusService;
|
final ConnectivityStatusService connectivityStatusService;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
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/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
@@ -9,13 +11,23 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
// try to parse contained error message, otherwise return response
|
// try to parse contained error message, otherwise return response
|
||||||
final dynamic data = err.response?.data;
|
final dynamic data = err.response?.data;
|
||||||
if (data is Map<String, dynamic>) {
|
if (data is Map<String, dynamic>) {
|
||||||
return _handlePaperlessValidationError(data, handler, err);
|
_handlePaperlessValidationError(data, handler, err);
|
||||||
} else if (data is String) {
|
} else if (data is String) {
|
||||||
return _handlePlainError(data, handler, err);
|
_handlePlainError(data, handler, err);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (err.error is SocketException) {
|
||||||
|
// Offline
|
||||||
|
handler.reject(
|
||||||
|
DioError(
|
||||||
|
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
||||||
|
requestOptions: err.requestOptions,
|
||||||
|
type: DioErrorType.connectTimeout,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
handler.reject(err);
|
handler.reject(err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handlePaperlessValidationError(
|
void _handlePaperlessValidationError(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
@@ -54,7 +66,8 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
DioError(
|
DioError(
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.response,
|
type: DioErrorType.response,
|
||||||
error: ErrorCode.missingClientCertificate,
|
error: const PaperlessServerException(
|
||||||
|
ErrorCode.missingClientCertificate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
|
|
||||||
class LanguageHeaderInterceptor extends Interceptor {
|
class LanguageHeaderInterceptor extends Interceptor {
|
||||||
String preferredLocaleSubtag;
|
String preferredLocaleSubtag;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class RetryOnConnectionChangeInterceptor extends Interceptor {
|
|||||||
try {
|
try {
|
||||||
handler.resolve(await DioHttpRequestRetrier(dio: dio)
|
handler.resolve(await DioHttpRequestRetrier(dio: dio)
|
||||||
.requestRetry(err.requestOptions)
|
.requestRetry(err.requestOptions)
|
||||||
|
// ignore: body_might_complete_normally_catch_error
|
||||||
.catchError((e) {
|
.catchError((e) {
|
||||||
handler.next(err);
|
handler.next(err);
|
||||||
}));
|
}));
|
||||||
|
|||||||
18
lib/core/repository/base_repository.dart
Normal file
18
lib/core/repository/base_repository.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
///
|
||||||
|
/// Base repository class which all repositories should implement
|
||||||
|
///
|
||||||
|
abstract class BaseRepository<State, Object> {
|
||||||
|
Stream<State?> get values;
|
||||||
|
|
||||||
|
State? get current;
|
||||||
|
|
||||||
|
bool get isInitialized;
|
||||||
|
|
||||||
|
Future<Object> create(Object object);
|
||||||
|
Future<Object?> find(int id);
|
||||||
|
Future<Iterable<Object>> findAll([Iterable<int>? ids]);
|
||||||
|
Future<Object> update(Object object);
|
||||||
|
Future<int> delete(Object object);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
}
|
||||||
@@ -7,35 +7,43 @@ import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
|||||||
class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
final _subject = BehaviorSubject<Map<int, Correspondent>>.seeded(const {});
|
final _subject = BehaviorSubject<Map<int, Correspondent>?>();
|
||||||
|
|
||||||
CorrespondentRepositoryImpl(this._api);
|
CorrespondentRepositoryImpl(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Map<int, Correspondent>> get labels =>
|
bool get isInitialized => _subject.valueOrNull != null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Map<int, Correspondent>?> get values =>
|
||||||
_subject.stream.asBroadcastStream();
|
_subject.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
Map<int, Correspondent> get _currentValueOrEmpty =>
|
||||||
|
_subject.valueOrNull ?? {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Correspondent> create(Correspondent correspondent) async {
|
Future<Correspondent> create(Correspondent correspondent) async {
|
||||||
final created = await _api.saveCorrespondent(correspondent);
|
final created = await _api.saveCorrespondent(correspondent);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(Correspondent correspondent) async {
|
Future<int> delete(Correspondent correspondent) async {
|
||||||
await _api.deleteCorrespondent(correspondent);
|
await _api.deleteCorrespondent(correspondent);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..removeWhere((k, v) => k == correspondent.id);
|
..removeWhere((k, v) => k == correspondent.id);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
|
return correspondent.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Correspondent?> find(int id) async {
|
Future<Correspondent?> find(int id) async {
|
||||||
final correspondent = await _api.getCorrespondent(id);
|
final correspondent = await _api.getCorrespondent(id);
|
||||||
if (correspondent != null) {
|
if (correspondent != null) {
|
||||||
final updatedState = {..._subject.value}..[id] = correspondent;
|
final updatedState = {..._currentValueOrEmpty}..[id] = correspondent;
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return correspondent;
|
return correspondent;
|
||||||
}
|
}
|
||||||
@@ -45,7 +53,7 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
||||||
final correspondents = await _api.getCorrespondents(ids);
|
final correspondents = await _api.getCorrespondents(ids);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return correspondents;
|
return correspondents;
|
||||||
@@ -54,7 +62,7 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
|||||||
@override
|
@override
|
||||||
Future<Correspondent> update(Correspondent correspondent) async {
|
Future<Correspondent> update(Correspondent correspondent) async {
|
||||||
final updated = await _api.updateCorrespondent(correspondent);
|
final updated = await _api.updateCorrespondent(correspondent);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..update(updated.id!, (_) => updated);
|
..update(updated.id!, (_) => updated);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return updated;
|
return updated;
|
||||||
@@ -62,9 +70,9 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void clear() {
|
void clear() {
|
||||||
_subject.add(const {});
|
_subject.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<int, Correspondent> get current => _subject.value;
|
Map<int, Correspondent>? get current => _subject.valueOrNull;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,42 @@ import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
|||||||
class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
final _subject = BehaviorSubject<Map<int, DocumentType>>.seeded(const {});
|
final _subject = BehaviorSubject<Map<int, DocumentType>?>();
|
||||||
|
|
||||||
DocumentTypeRepositoryImpl(this._api);
|
DocumentTypeRepositoryImpl(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Map<int, DocumentType>> get labels =>
|
Stream<Map<int, DocumentType>?> get values =>
|
||||||
_subject.stream.asBroadcastStream();
|
_subject.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isInitialized => _subject.valueOrNull != null;
|
||||||
|
|
||||||
|
Map<int, DocumentType> get _currentValueOrEmpty => _subject.valueOrNull ?? {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentType> create(DocumentType documentType) async {
|
Future<DocumentType> create(DocumentType documentType) async {
|
||||||
final created = await _api.saveDocumentType(documentType);
|
final created = await _api.saveDocumentType(documentType);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(DocumentType documentType) async {
|
Future<int> delete(DocumentType documentType) async {
|
||||||
await _api.deleteDocumentType(documentType);
|
await _api.deleteDocumentType(documentType);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..removeWhere((k, v) => k == documentType.id);
|
..removeWhere((k, v) => k == documentType.id);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
|
return documentType.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentType?> find(int id) async {
|
Future<DocumentType?> find(int id) async {
|
||||||
final documentType = await _api.getDocumentType(id);
|
final documentType = await _api.getDocumentType(id);
|
||||||
if (documentType != null) {
|
if (documentType != null) {
|
||||||
final updatedState = {..._subject.value}..[id] = documentType;
|
final updatedState = {..._currentValueOrEmpty}..[id] = documentType;
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return documentType;
|
return documentType;
|
||||||
}
|
}
|
||||||
@@ -44,7 +50,7 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
||||||
final documentTypes = await _api.getDocumentTypes(ids);
|
final documentTypes = await _api.getDocumentTypes(ids);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return documentTypes;
|
return documentTypes;
|
||||||
@@ -53,7 +59,7 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
|||||||
@override
|
@override
|
||||||
Future<DocumentType> update(DocumentType documentType) async {
|
Future<DocumentType> update(DocumentType documentType) async {
|
||||||
final updated = await _api.updateDocumentType(documentType);
|
final updated = await _api.updateDocumentType(documentType);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..update(updated.id!, (_) => updated);
|
..update(updated.id!, (_) => updated);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return updated;
|
return updated;
|
||||||
@@ -65,5 +71,5 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<int, DocumentType> get current => _subject.value;
|
Map<int, DocumentType>? get current => _subject.valueOrNull;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
|||||||
|
|
||||||
SavedViewRepositoryImpl(this._api);
|
SavedViewRepositoryImpl(this._api);
|
||||||
|
|
||||||
final BehaviorSubject<Map<int, SavedView>?> _subject = BehaviorSubject();
|
final _subject = BehaviorSubject<Map<int, SavedView>?>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Map<int, SavedView>?> get savedViews =>
|
Stream<Map<int, SavedView>?> get values =>
|
||||||
_subject.stream.asBroadcastStream();
|
_subject.stream.asBroadcastStream();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -54,4 +54,15 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
|||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<int, SavedView>? get current => _subject.valueOrNull;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isInitialized => _subject.hasValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SavedView> update(SavedView object) {
|
||||||
|
throw UnimplementedError("Saved view update is not yet implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,39 @@ import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
|||||||
class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
final _subject = BehaviorSubject<Map<int, StoragePath>>.seeded(const {});
|
final _subject = BehaviorSubject<Map<int, StoragePath>?>();
|
||||||
|
|
||||||
StoragePathRepositoryImpl(this._api);
|
StoragePathRepositoryImpl(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Map<int, StoragePath>> get labels =>
|
Stream<Map<int, StoragePath>?> get values =>
|
||||||
_subject.stream.asBroadcastStream();
|
_subject.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
Map<int, StoragePath> get _currentValueOrEmpty => _subject.valueOrNull ?? {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StoragePath> create(StoragePath storagePath) async {
|
Future<StoragePath> create(StoragePath storagePath) async {
|
||||||
final created = await _api.saveStoragePath(storagePath);
|
final created = await _api.saveStoragePath(storagePath);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(StoragePath storagePath) async {
|
Future<int> delete(StoragePath storagePath) async {
|
||||||
await _api.deleteStoragePath(storagePath);
|
await _api.deleteStoragePath(storagePath);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..removeWhere((k, v) => k == storagePath.id);
|
..removeWhere((k, v) => k == storagePath.id);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
|
return storagePath.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StoragePath?> find(int id) async {
|
Future<StoragePath?> find(int id) async {
|
||||||
final storagePath = await _api.getStoragePath(id);
|
final storagePath = await _api.getStoragePath(id);
|
||||||
if (storagePath != null) {
|
if (storagePath != null) {
|
||||||
final updatedState = {..._subject.value}..[id] = storagePath;
|
final updatedState = {..._currentValueOrEmpty}..[id] = storagePath;
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return storagePath;
|
return storagePath;
|
||||||
}
|
}
|
||||||
@@ -44,7 +47,7 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
||||||
final storagePaths = await _api.getStoragePaths(ids);
|
final storagePaths = await _api.getStoragePaths(ids);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return storagePaths;
|
return storagePaths;
|
||||||
@@ -53,7 +56,7 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
|||||||
@override
|
@override
|
||||||
Future<StoragePath> update(StoragePath storagePath) async {
|
Future<StoragePath> update(StoragePath storagePath) async {
|
||||||
final updated = await _api.updateStoragePath(storagePath);
|
final updated = await _api.updateStoragePath(storagePath);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..update(updated.id!, (_) => updated);
|
..update(updated.id!, (_) => updated);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return updated;
|
return updated;
|
||||||
@@ -65,5 +68,8 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<int, StoragePath> get current => _subject.value;
|
Map<int, StoragePath>? get current => _subject.valueOrNull;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isInitialized => _subject.valueOrNull != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,35 +5,38 @@ import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
|||||||
class TagRepositoryImpl implements LabelRepository<Tag> {
|
class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
final _subject = BehaviorSubject<Map<int, Tag>>.seeded(const {});
|
final _subject = BehaviorSubject<Map<int, Tag>?>();
|
||||||
|
|
||||||
TagRepositoryImpl(this._api);
|
TagRepositoryImpl(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Map<int, Tag>> get labels => _subject.stream.asBroadcastStream();
|
Stream<Map<int, Tag>?> get values => _subject.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
Map<int, Tag> get _currentValueOrEmpty => _subject.valueOrNull ?? {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Tag> create(Tag tag) async {
|
Future<Tag> create(Tag tag) async {
|
||||||
final created = await _api.saveTag(tag);
|
final created = await _api.saveTag(tag);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(Tag tag) async {
|
Future<int> delete(Tag tag) async {
|
||||||
await _api.deleteTag(tag);
|
await _api.deleteTag(tag);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..removeWhere((k, v) => k == tag.id);
|
..removeWhere((k, v) => k == tag.id);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
|
return tag.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Tag?> find(int id) async {
|
Future<Tag?> find(int id) async {
|
||||||
final tag = await _api.getTag(id);
|
final tag = await _api.getTag(id);
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
final updatedState = {..._subject.value}..[id] = tag;
|
final updatedState = {..._currentValueOrEmpty}..[id] = tag;
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
@@ -43,7 +46,7 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
||||||
final tags = await _api.getTags(ids);
|
final tags = await _api.getTags(ids);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return tags;
|
return tags;
|
||||||
@@ -52,7 +55,7 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
|||||||
@override
|
@override
|
||||||
Future<Tag> update(Tag tag) async {
|
Future<Tag> update(Tag tag) async {
|
||||||
final updated = await _api.updateTag(tag);
|
final updated = await _api.updateTag(tag);
|
||||||
final updatedState = {..._subject.value}
|
final updatedState = {..._currentValueOrEmpty}
|
||||||
..update(updated.id!, (_) => updated);
|
..update(updated.id!, (_) => updated);
|
||||||
_subject.add(updatedState);
|
_subject.add(updatedState);
|
||||||
return updated;
|
return updated;
|
||||||
@@ -60,9 +63,12 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void clear() {
|
void clear() {
|
||||||
_subject.add(const {});
|
_subject.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<int, Tag> get current => _subject.value;
|
Map<int, Tag>? get current => _subject.valueOrNull;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isInitialized => _subject.valueOrNull != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
||||||
|
|
||||||
abstract class LabelRepository<T extends Label> {
|
abstract class LabelRepository<T extends Label>
|
||||||
Stream<Map<int, T>> get labels;
|
implements BaseRepository<Map<int, T>, T> {}
|
||||||
|
|
||||||
Map<int, T> get current;
|
|
||||||
|
|
||||||
Future<T> create(T label);
|
|
||||||
Future<T?> find(int id);
|
|
||||||
Future<Iterable<T>> findAll([Iterable<int>? ids]);
|
|
||||||
Future<T> update(T label);
|
|
||||||
Future<void> delete(T label);
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
||||||
|
|
||||||
abstract class SavedViewRepository {
|
abstract class SavedViewRepository
|
||||||
Stream<Map<int, SavedView>?> get savedViews;
|
implements BaseRepository<Map<int, SavedView>, SavedView> {}
|
||||||
|
|
||||||
Future<SavedView> create(SavedView view);
|
|
||||||
Future<SavedView?> find(int id);
|
|
||||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]);
|
|
||||||
Future<int> delete(SavedView view);
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/adapter.dart';
|
import 'package:dio/adapter.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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/extensions/security_context_extension.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
|
||||||
class AuthenticationAwareDioManager {
|
class AuthenticationAwareDioManager {
|
||||||
final Dio client;
|
final Dio client;
|
||||||
final List<Interceptor> interceptors;
|
final List<Interceptor> interceptors;
|
||||||
|
|
||||||
/// Some dependencies require an [HttpClient], therefore this is also maintained here.
|
|
||||||
|
|
||||||
AuthenticationAwareDioManager([this.interceptors = const []])
|
AuthenticationAwareDioManager([this.interceptors = const []])
|
||||||
: client = _initDio(interceptors);
|
: client = _initDio(interceptors);
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:http/io_client.dart';
|
|
||||||
import 'package:http_interceptor/http_interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
|
|
||||||
extension SecurityContextAwareBaseClientSubjectExtension
|
|
||||||
on BehaviorSubject<BaseClient> {
|
|
||||||
///
|
|
||||||
/// Registers new security context in a new [HttpClient].
|
|
||||||
///
|
|
||||||
|
|
||||||
BaseClient _createSecurityContextAwareHttpClient(
|
|
||||||
SecurityContext context, {
|
|
||||||
List<InterceptorContract> interceptors = const [],
|
|
||||||
}) {
|
|
||||||
Dio(BaseOptions());
|
|
||||||
return InterceptedClient.build(
|
|
||||||
client: IOClient(HttpClient(context: context)),
|
|
||||||
interceptors: interceptors,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
||||||
|
|
||||||
abstract class ConnectivityStatusService {
|
|
||||||
Future<bool> isConnectedToInternet();
|
|
||||||
Future<bool> isServerReachable(String serverAddress);
|
|
||||||
Stream<bool> connectivityChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|
||||||
final Connectivity connectivity;
|
|
||||||
|
|
||||||
ConnectivityStatusServiceImpl(this.connectivity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<bool> connectivityChanges() {
|
|
||||||
return connectivity.onConnectivityChanged
|
|
||||||
.map(_hasActiveInternetConnection)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isConnectedToInternet() async {
|
|
||||||
return _hasActiveInternetConnection(
|
|
||||||
await (Connectivity().checkConnectivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isServerReachable(String serverAddress) async {
|
|
||||||
try {
|
|
||||||
var uri = Uri.parse(serverAddress);
|
|
||||||
final result = await InternetAddress.lookup(uri.host);
|
|
||||||
if (result.isNotEmpty && result.first.rawAddress.isNotEmpty) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} on SocketException catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _hasActiveInternetConnection(ConnectivityResult conn) {
|
|
||||||
return conn == ConnectivityResult.mobile ||
|
|
||||||
conn == ConnectivityResult.wifi ||
|
|
||||||
conn == ConnectivityResult.ethernet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
113
lib/core/service/connectivity_status_service.dart
Normal file
113
lib/core/service/connectivity_status_service.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:dio/adapter.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||||
|
|
||||||
|
abstract class ConnectivityStatusService {
|
||||||
|
Future<bool> isConnectedToInternet();
|
||||||
|
Future<bool> isServerReachable(String serverAddress);
|
||||||
|
Stream<bool> connectivityChanges();
|
||||||
|
Future<ReachabilityStatus> isPaperlessServerReachable(
|
||||||
|
String serverAddress, [
|
||||||
|
ClientCertificate? clientCertificate,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
||||||
|
final Connectivity _connectivity;
|
||||||
|
|
||||||
|
ConnectivityStatusServiceImpl(this._connectivity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<bool> connectivityChanges() {
|
||||||
|
return _connectivity.onConnectivityChanged
|
||||||
|
.map(_hasActiveInternetConnection)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isConnectedToInternet() async {
|
||||||
|
return _hasActiveInternetConnection(
|
||||||
|
await (Connectivity().checkConnectivity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isServerReachable(String serverAddress) async {
|
||||||
|
try {
|
||||||
|
var uri = Uri.parse(serverAddress);
|
||||||
|
final result = await InternetAddress.lookup(uri.host);
|
||||||
|
if (result.isNotEmpty && result.first.rawAddress.isNotEmpty) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} on SocketException catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasActiveInternetConnection(ConnectivityResult conn) {
|
||||||
|
return conn == ConnectivityResult.mobile ||
|
||||||
|
conn == ConnectivityResult.wifi ||
|
||||||
|
conn == ConnectivityResult.ethernet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ReachabilityStatus> isPaperlessServerReachable(
|
||||||
|
String serverAddress, [
|
||||||
|
ClientCertificate? clientCertificate,
|
||||||
|
]) async {
|
||||||
|
if (!RegExp(r"^https?://.*").hasMatch(serverAddress)) {
|
||||||
|
return ReachabilityStatus.unknown;
|
||||||
|
}
|
||||||
|
late SecurityContext context = SecurityContext();
|
||||||
|
try {
|
||||||
|
if (clientCertificate != null) {
|
||||||
|
context
|
||||||
|
..usePrivateKeyBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
)
|
||||||
|
..useCertificateChainBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
)
|
||||||
|
..setTrustedCertificatesBytes(
|
||||||
|
clientCertificate.bytes,
|
||||||
|
password: clientCertificate.passphrase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final adapter = DefaultHttpClientAdapter()
|
||||||
|
..onHttpClientCreate = (client) => HttpClient(context: context)
|
||||||
|
..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
final Dio dio = Dio()..httpClientAdapter = adapter;
|
||||||
|
|
||||||
|
final response = await dio.get('$serverAddress/api/');
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return ReachabilityStatus.reachable;
|
||||||
|
}
|
||||||
|
return ReachabilityStatus.notReachable;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
if (error.error is String) {
|
||||||
|
if (error.response?.data is String) {
|
||||||
|
if ((error.response!.data as String)
|
||||||
|
.contains("No required SSL certificate was sent")) {
|
||||||
|
return ReachabilityStatus.missingClientCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ReachabilityStatus.notReachable;
|
||||||
|
} on TlsException catch (error) {
|
||||||
|
if (error.osError?.errorCode == 318767212) {
|
||||||
|
//INCORRECT_PASSWORD for certificate
|
||||||
|
return ReachabilityStatus.invalidClientCertificateConfiguration;
|
||||||
|
}
|
||||||
|
return ReachabilityStatus.notReachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/relative_date_range_picker_helper.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class FormBuilderRelativeDateRangePicker extends StatefulWidget {
|
class FormBuilderRelativeDateRangePicker extends StatefulWidget {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class OfflineBanner extends StatelessWidget with PreferredSizeWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.cloud_off,
|
Icons.cloud_off,
|
||||||
size: 24,
|
size: 24,
|
||||||
|
|||||||
@@ -4,7 +4,16 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
class PaperlessLogo extends StatelessWidget {
|
class PaperlessLogo extends StatelessWidget {
|
||||||
final double? height;
|
final double? height;
|
||||||
final double? width;
|
final double? width;
|
||||||
const PaperlessLogo({Key? key, this.height, this.width}) : super(key: key);
|
final String _path;
|
||||||
|
|
||||||
|
const PaperlessLogo.white({super.key, this.height, this.width})
|
||||||
|
: _path = "assets/logos/paperless_logo_white.svg";
|
||||||
|
|
||||||
|
const PaperlessLogo.green({super.key, this.height, this.width})
|
||||||
|
: _path = "assets/logos/paperless_logo_green.svg";
|
||||||
|
|
||||||
|
const PaperlessLogo.black({super.key, this.height, this.width})
|
||||||
|
: _path = "assets/logos/paperless_logo_black.svg";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -15,8 +24,7 @@ class PaperlessLogo extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
"assets/logo/paperless_ng_logo_light.svg",
|
_path,
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:introduction_screen/introduction_screen.dart';
|
import 'package:introduction_screen/introduction_screen.dart';
|
||||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/theme_mode_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/theme_mode_setting.dart';
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ConfigurationDoneIntroSlide extends StatelessWidget {
|
|
||||||
const ConfigurationDoneIntroSlide({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
//TODO: INTL
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"All set up!",
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.emoji_emotions_outlined,
|
|
||||||
size: 64,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"You've successfully configured Paperless Mobile! Press 'GO' to get started managing your documents.",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -449,7 +449,7 @@ class _DetailsItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: Theme.of(context).textTheme.caption,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
content,
|
content,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:paperless_mobile/core/store/local_vault.dart';
|
|||||||
part 'document_upload_state.dart';
|
part 'document_upload_state.dart';
|
||||||
|
|
||||||
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||||
final LocalVault _localVault;
|
|
||||||
final PaperlessDocumentsApi _documentApi;
|
final PaperlessDocumentsApi _documentApi;
|
||||||
|
|
||||||
final LabelRepository<Tag> _tagRepository;
|
final LabelRepository<Tag> _tagRepository;
|
||||||
@@ -29,7 +28,6 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
_tagRepository = tagRepository,
|
_tagRepository = tagRepository,
|
||||||
_correspondentRepository = correspondentRepository,
|
_correspondentRepository = correspondentRepository,
|
||||||
_documentTypeRepository = documentTypeRepository,
|
_documentTypeRepository = documentTypeRepository,
|
||||||
_localVault = localVault,
|
|
||||||
super(
|
super(
|
||||||
const DocumentUploadState(
|
const DocumentUploadState(
|
||||||
tags: {},
|
tags: {},
|
||||||
@@ -37,13 +35,13 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
documentTypes: {},
|
documentTypes: {},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
_subs.add(_tagRepository.labels.listen(
|
_subs.add(_tagRepository.values.listen(
|
||||||
(tags) => emit(state.copyWith(tags: tags)),
|
(tags) => emit(state.copyWith(tags: tags)),
|
||||||
));
|
));
|
||||||
_subs.add(_correspondentRepository.labels.listen(
|
_subs.add(_correspondentRepository.values.listen(
|
||||||
(correspondents) => emit(state.copyWith(correspondents: correspondents)),
|
(correspondents) => emit(state.copyWith(correspondents: correspondents)),
|
||||||
));
|
));
|
||||||
_subs.add(_documentTypeRepository.labels.listen(
|
_subs.add(_documentTypeRepository.values.listen(
|
||||||
(documentTypes) => emit(state.copyWith(documentTypes: documentTypes)),
|
(documentTypes) => emit(state.copyWith(documentTypes: documentTypes)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'dart:developer' as dev;
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
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:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
@@ -14,7 +12,6 @@ import 'package:paperless_mobile/features/labels/bloc/label_state.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.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
|
||||||
|
|
||||||
enum DateRangeSelection { before, after }
|
enum DateRangeSelection { before, after }
|
||||||
|
|
||||||
|
|||||||
@@ -34,26 +34,26 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
super(
|
super(
|
||||||
EditDocumentState(
|
EditDocumentState(
|
||||||
document: document,
|
document: document,
|
||||||
correspondents: correspondentRepository.current,
|
correspondents: correspondentRepository.current ?? {},
|
||||||
documentTypes: documentTypeRepository.current,
|
documentTypes: documentTypeRepository.current ?? {},
|
||||||
storagePaths: storagePathRepository.current,
|
storagePaths: storagePathRepository.current ?? {},
|
||||||
tags: tagRepository.current,
|
tags: tagRepository.current ?? {},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_correspondentRepository.labels
|
_correspondentRepository.values
|
||||||
.listen((v) => emit(state.copyWith(correspondents: v))),
|
.listen((v) => emit(state.copyWith(correspondents: v))),
|
||||||
);
|
);
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_documentTypeRepository.labels
|
_documentTypeRepository.values
|
||||||
.listen((v) => emit(state.copyWith(documentTypes: v))),
|
.listen((v) => emit(state.copyWith(documentTypes: v))),
|
||||||
);
|
);
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_storagePathRepository.labels
|
_storagePathRepository.values
|
||||||
.listen((v) => emit(state.copyWith(storagePaths: v))),
|
.listen((v) => emit(state.copyWith(storagePaths: v))),
|
||||||
);
|
);
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_tagRepository.labels.listen(
|
_tagRepository.values.listen(
|
||||||
(v) => emit(state.copyWith(tags: v)),
|
(v) => emit(state.copyWith(tags: v)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart
|
|||||||
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
||||||
final LabelRepository<T> _repository;
|
final LabelRepository<T> _repository;
|
||||||
|
|
||||||
StreamSubscription<Map<int, T>>? _subscription;
|
StreamSubscription<Map<int, T>?>? _subscription;
|
||||||
|
|
||||||
EditLabelCubit(LabelRepository<T> repository)
|
EditLabelCubit(LabelRepository<T> repository)
|
||||||
: _repository = repository,
|
: _repository = repository,
|
||||||
super(const EditLabelInitial()) {
|
super(const EditLabelInitial()) {
|
||||||
_subscription = _repository.labels
|
_subscription = repository.values.listen(
|
||||||
.listen((labels) => emit(EditLabelState(labels: labels)));
|
(update) => emit(EditLabelState(labels: update ?? {})),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T> create(T label) => _repository.create(label);
|
Future<T> create(T label) => _repository.create(label);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
@@ -5,6 +6,7 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
|||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|
||||||
class EditLabelPage<T extends Label> extends StatelessWidget {
|
class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||||
final T label;
|
final T label;
|
||||||
@@ -91,7 +93,8 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).genericActionDeleteLabel,
|
S.of(context).genericActionDeleteLabel,
|
||||||
style: TextStyle(color: Theme.of(context).errorColor),
|
style:
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -99,7 +102,13 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
|||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
|
try {
|
||||||
context.read<EditLabelCubit<T>>().delete(label);
|
context.read<EditLabelCubit<T>>().delete(label);
|
||||||
|
} on PaperlessServerException catch (error) {
|
||||||
|
showErrorMessage(context, error);
|
||||||
|
} catch (error) {
|
||||||
|
print(error);
|
||||||
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
|||||||
Navigator.pop(context, createdLabel);
|
Navigator.pop(context, createdLabel);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} on DioError catch (error) {
|
|
||||||
setState(() => _errors = error.error as PaperlessValidationErrors);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ class InfoDrawer extends StatefulWidget {
|
|||||||
State<InfoDrawer> createState() => _InfoDrawerState();
|
State<InfoDrawer> createState() => _InfoDrawerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NavigationDestinations {
|
||||||
|
inbox,
|
||||||
|
settings,
|
||||||
|
reportBug,
|
||||||
|
about,
|
||||||
|
logout;
|
||||||
|
}
|
||||||
|
|
||||||
class _InfoDrawerState extends State<InfoDrawer> {
|
class _InfoDrawerState extends State<InfoDrawer> {
|
||||||
late final Future<PackageInfo> _packageInfo;
|
late final Future<PackageInfo> _packageInfo;
|
||||||
|
|
||||||
@@ -41,6 +49,59 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final listtTileShape = RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
);
|
||||||
|
// return NavigationDrawer(
|
||||||
|
// selectedIndex: -1,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// "",
|
||||||
|
// style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
// ).padded(16),
|
||||||
|
// NavigationDrawerDestination(
|
||||||
|
// icon: const Icon(Icons.inbox),
|
||||||
|
// label: Text(S.of(context).bottomNavInboxPageLabel),
|
||||||
|
// ),
|
||||||
|
// NavigationDrawerDestination(
|
||||||
|
// icon: const Icon(Icons.settings),
|
||||||
|
// label: Text(S.of(context).appDrawerSettingsLabel),
|
||||||
|
// ),
|
||||||
|
// const Divider(
|
||||||
|
// indent: 16,
|
||||||
|
// ),
|
||||||
|
// NavigationDrawerDestination(
|
||||||
|
// icon: const Icon(Icons.bug_report),
|
||||||
|
// label: Text(S.of(context).appDrawerReportBugLabel),
|
||||||
|
// ),
|
||||||
|
// NavigationDrawerDestination(
|
||||||
|
// icon: const Icon(Icons.info_outline),
|
||||||
|
// label: Text(S.of(context).appDrawerAboutLabel),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// onDestinationSelected: (idx) {
|
||||||
|
// final val = NavigationDestinations.values[idx - 1];
|
||||||
|
// switch (val) {
|
||||||
|
// case NavigationDestinations.inbox:
|
||||||
|
// _onOpenInbox();
|
||||||
|
// break;
|
||||||
|
// case NavigationDestinations.settings:
|
||||||
|
// _onOpenSettings();
|
||||||
|
// break;
|
||||||
|
// case NavigationDestinations.reportBug:
|
||||||
|
// launchUrlString(
|
||||||
|
// 'https://github.com/astubenbord/paperless-mobile/issues/new',
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// case NavigationDestinations.about:
|
||||||
|
// _onShowAboutDialog();
|
||||||
|
// break;
|
||||||
|
// case NavigationDestinations.logout:
|
||||||
|
// _onLogout();
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// );
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topRight: Radius.circular(16.0),
|
topRight: Radius.circular(16.0),
|
||||||
@@ -146,10 +207,12 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||||
leading: const Icon(Icons.inbox),
|
leading: const Icon(Icons.inbox),
|
||||||
onTap: () => _onOpenInbox(context),
|
onTap: () => _onOpenInbox(),
|
||||||
|
shape: listtTileShape,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
|
shape: listtTileShape,
|
||||||
title: Text(
|
title: Text(
|
||||||
S.of(context).appDrawerSettingsLabel,
|
S.of(context).appDrawerSettingsLabel,
|
||||||
),
|
),
|
||||||
@@ -169,49 +232,30 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
launchUrlString(
|
launchUrlString(
|
||||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||||
},
|
},
|
||||||
|
shape: listtTileShape,
|
||||||
),
|
),
|
||||||
FutureBuilder<PackageInfo>(
|
ListTile(
|
||||||
future: _packageInfo,
|
title: Text(S.of(context).appDrawerAboutLabel),
|
||||||
builder: (context, snapshot) {
|
leading: Icon(Icons.info_outline_rounded),
|
||||||
return AboutListTile(
|
onTap: _onShowAboutDialog,
|
||||||
icon: const Icon(Icons.info),
|
shape: listtTileShape,
|
||||||
applicationIcon: const ImageIcon(
|
|
||||||
AssetImage('assets/logos/paperless_logo_green.png'),
|
|
||||||
),
|
),
|
||||||
applicationName: 'Paperless Mobile',
|
|
||||||
applicationVersion: (snapshot.data?.version ?? '') +
|
|
||||||
'+' +
|
|
||||||
(snapshot.data?.buildNumber ?? ''),
|
|
||||||
aboutBoxChildren: [
|
|
||||||
Text(
|
|
||||||
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
|
||||||
Link(
|
|
||||||
uri: Uri.parse(
|
|
||||||
'https://github.com/astubenbord/paperless-mobile'),
|
|
||||||
builder: (context, followLink) => GestureDetector(
|
|
||||||
onTap: followLink,
|
|
||||||
child: Text(
|
|
||||||
'https://github.com/astubenbord/paperless-mobile',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.tertiary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Credits',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
_buildOnboardingImageCredits(),
|
|
||||||
],
|
|
||||||
child: Text(S.of(context).appDrawerAboutLabel),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.logout),
|
leading: const Icon(Icons.logout),
|
||||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||||
|
shape: listtTileShape,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
_onLogout();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLogout() {
|
||||||
try {
|
try {
|
||||||
context.read<AuthenticationCubit>().logout();
|
context.read<AuthenticationCubit>().logout();
|
||||||
context.read<LocalVault>().clear();
|
context.read<LocalVault>().clear();
|
||||||
@@ -224,16 +268,9 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
)
|
|
||||||
].expand((element) => [element, const Divider()]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onOpenInbox(BuildContext context) async {
|
Future<void> _onOpenInbox() async {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => LabelRepositoriesProvider(
|
builder: (_) => LabelRepositoriesProvider(
|
||||||
@@ -251,6 +288,17 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
widget.afterInboxClosed?.call();
|
widget.afterInboxClosed?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onOpenSettings() {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => BlocProvider.value(
|
||||||
|
value: context.read<ApplicationSettingsCubit>(),
|
||||||
|
child: const SettingsPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Link _buildOnboardingImageCredits() {
|
Link _buildOnboardingImageCredits() {
|
||||||
return Link(
|
return Link(
|
||||||
uri: Uri.parse(
|
uri: Uri.parse(
|
||||||
@@ -270,4 +318,35 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onShowAboutDialog() async {
|
||||||
|
final snapshot = await _packageInfo;
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationIcon: const ImageIcon(
|
||||||
|
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||||
|
),
|
||||||
|
applicationName: 'Paperless Mobile',
|
||||||
|
applicationVersion: snapshot.version + '+' + snapshot.buildNumber,
|
||||||
|
children: [
|
||||||
|
Text('${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
||||||
|
Link(
|
||||||
|
uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
|
||||||
|
builder: (context, followLink) => GestureDetector(
|
||||||
|
onTap: followLink,
|
||||||
|
child: Text(
|
||||||
|
'https://github.com/astubenbord/paperless-mobile',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Credits',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
_buildOnboardingImageCredits(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).inboxPageUsageHintText,
|
S.of(context).inboxPageUsageHintText,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.caption,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
).padded(),
|
).padded(),
|
||||||
),
|
),
|
||||||
...slivers
|
...slivers
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:paperless_mobile/features/document_details/bloc/document_details
|
|||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class InboxItem extends StatelessWidget {
|
class InboxItem extends StatelessWidget {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.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/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
@@ -13,11 +12,17 @@ class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
|||||||
|
|
||||||
LabelCubit(LabelRepository<T> repository)
|
LabelCubit(LabelRepository<T> repository)
|
||||||
: _repository = repository,
|
: _repository = repository,
|
||||||
super(LabelState(labels: repository.current, isLoaded: true)) {
|
super(LabelState(
|
||||||
_subscription = _repository.labels.listen(
|
isLoaded: repository.isInitialized,
|
||||||
(update) => emit(
|
labels: repository.current ?? {},
|
||||||
LabelState(isLoaded: true, labels: update),
|
)) {
|
||||||
),
|
_subscription = _repository.values.listen(
|
||||||
|
(update) {
|
||||||
|
if (update == null) {
|
||||||
|
emit(LabelState());
|
||||||
|
}
|
||||||
|
emit(LabelState(isLoaded: true, labels: update!));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ class LabelState<T extends Label> {
|
|||||||
final Map<int, T> labels;
|
final Map<int, T> labels;
|
||||||
|
|
||||||
LabelState({
|
LabelState({
|
||||||
required this.isLoaded,
|
this.isLoaded = false,
|
||||||
required this.labels,
|
this.labels = const {},
|
||||||
});
|
});
|
||||||
|
|
||||||
T? getLabel(int? key) {
|
T? getLabel(int? key) {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
||||||
|
|
||||||
class CorrespondentWidget extends StatelessWidget {
|
class CorrespondentWidget extends StatelessWidget {
|
||||||
final int? correspondentId;
|
final int? correspondentId;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/src/widgets/framework.dart';
|
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_typ
|
|||||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
||||||
|
|||||||
@@ -3,7 +3,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/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class LabelItem<T extends Label> extends StatelessWidget {
|
class LabelItem<T extends Label> extends StatelessWidget {
|
||||||
final T label;
|
final T label;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||||
@@ -38,12 +37,12 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, state) {
|
builder: (context, connectivityState) {
|
||||||
if (state == ConnectivityState.notConnected) {
|
|
||||||
return const OfflineWidget();
|
|
||||||
}
|
|
||||||
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
if (!state.isLoaded && !connectivityState.isConnected) {
|
||||||
|
return const OfflineWidget();
|
||||||
|
}
|
||||||
final labels = state.labels.values.toList()..sort();
|
final labels = state.labels.values.toList()..sort();
|
||||||
if (labels.isEmpty) {
|
if (labels.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
@@ -57,13 +56,15 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: onAddNew,
|
onPressed: onAddNew,
|
||||||
child: Text(emptyStateActionButtonLabel),
|
child: Text(emptyStateActionButtonLabel),
|
||||||
)
|
),
|
||||||
].padded(),
|
].padded(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: context.read<LabelCubit<T>>().reload,
|
onRefresh: context.read<LabelCubit<T>>().reload,
|
||||||
|
notificationPredicate: (notification) =>
|
||||||
|
connectivityState.isConnected,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: labels
|
children: labels
|
||||||
.map(
|
.map(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:paperless_mobile/features/documents/view/widgets/list/document_l
|
|||||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class LinkedDocumentsPage extends StatefulWidget {
|
class LinkedDocumentsPage extends StatefulWidget {
|
||||||
const LinkedDocumentsPage({super.key});
|
const LinkedDocumentsPage({super.key});
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/security/authentication_aware_dio_manager.dart';
|
import 'package:paperless_mobile/core/security/authentication_aware_dio_manager.dart';
|
||||||
@@ -27,13 +25,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState>
|
|||||||
ClientCertificate? clientCertificate,
|
ClientCertificate? clientCertificate,
|
||||||
}) async {
|
}) async {
|
||||||
assert(credentials.username != null && credentials.password != null);
|
assert(credentials.username != null && credentials.password != null);
|
||||||
try {
|
|
||||||
print(_dioWrapper.client.hashCode);
|
|
||||||
_dioWrapper.updateSettings(
|
_dioWrapper.updateSettings(
|
||||||
baseUrl: serverUrl,
|
baseUrl: serverUrl,
|
||||||
clientCertificate: clientCertificate,
|
clientCertificate: clientCertificate,
|
||||||
);
|
);
|
||||||
|
|
||||||
final token = await _authApi.login(
|
final token = await _authApi.login(
|
||||||
username: credentials.username!,
|
username: credentials.username!,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
@@ -55,17 +51,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on TlsException catch (_) {
|
|
||||||
const error = PaperlessServerException(
|
|
||||||
ErrorCode.invalidClientCertificateConfiguration);
|
|
||||||
throw error;
|
|
||||||
} on SocketException catch (err) {
|
|
||||||
if (err.message.contains("connection timed out")) {
|
|
||||||
throw const PaperlessServerException(ErrorCode.requestTimedOut);
|
|
||||||
} else {
|
|
||||||
throw const PaperlessServerException.unknown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
8
lib/features/login/model/reachability_status.dart
Normal file
8
lib/features/login/model/reachability_status.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
enum ReachabilityStatus {
|
||||||
|
unknown,
|
||||||
|
reachable,
|
||||||
|
notReachable,
|
||||||
|
unknownHost,
|
||||||
|
missingClientCertificate,
|
||||||
|
invalidClientCertificateConfiguration,
|
||||||
|
}
|
||||||
@@ -2,14 +2,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
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/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/client_certificate_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/client_certificate_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/server_address_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/server_address_form_field.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/view/widgets/server_connection_page.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/user_credentials_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/user_credentials_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|
||||||
|
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||||
|
import 'widgets/server_login_page.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({Key? key}) : super(key: key);
|
const LoginPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -20,53 +23,73 @@ class LoginPage extends StatefulWidget {
|
|||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
bool _isLoginLoading = false;
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: false, // appBar: AppBar(
|
||||||
appBar: AppBar(
|
// title: Text(S.of(context).loginPageTitle),
|
||||||
title: Text(S.of(context).loginPageTitle),
|
// bottom: _isLoginLoading
|
||||||
bottom: _isLoginLoading
|
// ? const PreferredSize(
|
||||||
? const PreferredSize(
|
// preferredSize: Size(double.infinity, 4),
|
||||||
preferredSize: Size(double.infinity, 4),
|
// child: LinearProgressIndicator(),
|
||||||
child: LinearProgressIndicator(),
|
// )
|
||||||
)
|
// : null,
|
||||||
: null,
|
// ),
|
||||||
),
|
body: FormBuilder(
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: FormBuilder(
|
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: PageView(
|
||||||
|
controller: _pageController,
|
||||||
|
scrollBehavior: NeverScrollableScrollBehavior(),
|
||||||
children: [
|
children: [
|
||||||
const ServerAddressFormField().padded(),
|
ServerConnectionPage(
|
||||||
const UserCredentialsFormField(),
|
formBuilderKey: _formKey,
|
||||||
Align(
|
onContinue: () {
|
||||||
alignment: Alignment.centerLeft,
|
_pageController.nextPage(
|
||||||
child: Padding(
|
duration: const Duration(milliseconds: 300),
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
curve: Curves.easeInOut);
|
||||||
child: Text(
|
},
|
||||||
S.of(context).loginPageAdvancedLabel,
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
).padded(),
|
|
||||||
),
|
),
|
||||||
|
ServerLoginPage(
|
||||||
|
formBuilderKey: _formKey,
|
||||||
|
onDone: _login,
|
||||||
),
|
),
|
||||||
const ClientCertificateFormField(),
|
|
||||||
LayoutBuilder(builder: (context, constraints) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: SizedBox(
|
|
||||||
width: constraints.maxWidth,
|
|
||||||
child: _buildLoginButton(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.all(8.0),
|
||||||
|
// child: FormBuilder(
|
||||||
|
// key: _formKey,
|
||||||
|
// child: ListView(
|
||||||
|
// children: [
|
||||||
|
// const ServerAddressFormField().padded(),
|
||||||
|
// const UserCredentialsFormField(),
|
||||||
|
// Align(
|
||||||
|
// alignment: Alignment.centerLeft,
|
||||||
|
// child: Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
// child: Text(
|
||||||
|
// S.of(context).loginPageAdvancedLabel,
|
||||||
|
// style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
// ).padded(),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const ClientCertificateFormField(),
|
||||||
|
// LayoutBuilder(builder: (context, constraints) {
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.all(8.0),
|
||||||
|
// child: SizedBox(
|
||||||
|
// width: constraints.maxWidth,
|
||||||
|
// child: _buildLoginButton(),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +112,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
void _login() async {
|
void _login() async {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
setState(() => _isLoginLoading = true);
|
|
||||||
final form = _formKey.currentState!.value;
|
final form = _formKey.currentState!.value;
|
||||||
try {
|
try {
|
||||||
await context.read<AuthenticationCubit>().login(
|
await context.read<AuthenticationCubit>().login(
|
||||||
@@ -104,9 +126,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
showGenericError(context, error.values.first, stackTrace);
|
showGenericError(context, error.values.first, stackTrace);
|
||||||
} catch (unknownError, stackTrace) {
|
} catch (unknownError, stackTrace) {
|
||||||
showGenericError(context, unknownError.toString(), stackTrace);
|
showGenericError(context, unknownError.toString(), stackTrace);
|
||||||
} finally {
|
} finally {}
|
||||||
setState(() => _isLoginLoading = false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
|||||||
|
|
||||||
class ClientCertificateFormField extends StatefulWidget {
|
class ClientCertificateFormField extends StatefulWidget {
|
||||||
static const fkClientCertificate = 'clientCertificate';
|
static const fkClientCertificate = 'clientCertificate';
|
||||||
const ClientCertificateFormField({Key? key}) : super(key: key);
|
|
||||||
|
final void Function(ClientCertificate? cert) onChanged;
|
||||||
|
const ClientCertificateFormField({
|
||||||
|
Key? key,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ClientCertificateFormField> createState() =>
|
State<ClientCertificateFormField> createState() =>
|
||||||
@@ -19,11 +24,13 @@ class ClientCertificateFormField extends StatefulWidget {
|
|||||||
|
|
||||||
class _ClientCertificateFormFieldState
|
class _ClientCertificateFormFieldState
|
||||||
extends State<ClientCertificateFormField> {
|
extends State<ClientCertificateFormField> {
|
||||||
|
RestorableString? _selectedFilePath;
|
||||||
File? _selectedFile;
|
File? _selectedFile;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FormBuilderField<ClientCertificate?>(
|
return FormBuilderField<ClientCertificate?>(
|
||||||
key: const ValueKey('login-client-cert'),
|
key: const ValueKey('login-client-cert'),
|
||||||
|
onChanged: widget.onChanged,
|
||||||
initialValue: null,
|
initialValue: null,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -38,7 +45,11 @@ class _ClientCertificateFormFieldState
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
builder: (field) {
|
builder: (field) {
|
||||||
return ExpansionTile(
|
final theme =
|
||||||
|
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
||||||
|
return Theme(
|
||||||
|
data: theme,
|
||||||
|
child: ExpansionTile(
|
||||||
title: Text(S.of(context).loginPageClientCertificateSettingLabel),
|
title: Text(S.of(context).loginPageClientCertificateSettingLabel),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
S.of(context).loginPageClientCertificateSettingDescriptionText),
|
S.of(context).loginPageClientCertificateSettingDescriptionText),
|
||||||
@@ -86,6 +97,7 @@ class _ClientCertificateFormFieldState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
name: ClientCertificateFormField.fkClientCertificate,
|
name: ClientCertificateFormField.fkClientCertificate,
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class NeverScrollableScrollBehavior extends ScrollBehavior {
|
||||||
|
@override
|
||||||
|
ScrollPhysics getScrollPhysics(BuildContext context) {
|
||||||
|
return const NeverScrollableScrollPhysics();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ServerAddressFormField extends StatefulWidget {
|
class ServerAddressFormField extends StatefulWidget {
|
||||||
static const String fkServerAddress = "serverAddress";
|
static const String fkServerAddress = "serverAddress";
|
||||||
|
|
||||||
|
final void Function(String address) onDone;
|
||||||
const ServerAddressFormField({
|
const ServerAddressFormField({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
required this.onDone,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -17,13 +20,7 @@ class ServerAddressFormField extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||||
static const _ipv4Regex = r"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}";
|
|
||||||
static const _ipv6Regex =
|
|
||||||
r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))";
|
|
||||||
static final _urlRegex = RegExp(
|
|
||||||
r"^(https?:\/\/)(([\da-z\.-]+)\.([a-z\.]{2,6})|(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4})|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))))(:\d{1,5})?([\/\w \.-]*)*\/?$");
|
|
||||||
final TextEditingController _textEditingController = TextEditingController();
|
final TextEditingController _textEditingController = TextEditingController();
|
||||||
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.undefined;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -31,75 +28,25 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
key: const ValueKey('login-server-address'),
|
key: const ValueKey('login-server-address'),
|
||||||
controller: _textEditingController,
|
controller: _textEditingController,
|
||||||
name: ServerAddressFormField.fkServerAddress,
|
name: ServerAddressFormField.fkServerAddress,
|
||||||
validator: FormBuilderValidators.compose(
|
validator: FormBuilderValidators.required(
|
||||||
[
|
errorText: S.of(context).loginPageServerUrlValidatorMessageRequiredText,
|
||||||
FormBuilderValidators.required(
|
|
||||||
errorText:
|
|
||||||
S.of(context).loginPageServerUrlValidatorMessageRequiredText,
|
|
||||||
),
|
|
||||||
FormBuilderValidators.match(
|
|
||||||
_urlRegex.pattern,
|
|
||||||
errorText: S
|
|
||||||
.of(context)
|
|
||||||
.loginPageServerUrlValidatorMessageInvalidAddressText,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
suffixIcon: _buildIsReachableIcon(),
|
|
||||||
hintText: "http://192.168.1.50:8000",
|
hintText: "http://192.168.1.50:8000",
|
||||||
labelText: S.of(context).loginPageServerUrlFieldLabel,
|
labelText: S.of(context).loginPageServerUrlFieldLabel,
|
||||||
),
|
),
|
||||||
onChanged: _updateIsAddressReachableStatus,
|
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
// Remove trailing slash if it is a valid address.
|
// Remove trailing slash if it is a valid address.
|
||||||
final address = value.trim();
|
String address = value.trim();
|
||||||
|
address = _replaceTrailingSlashes(address);
|
||||||
_textEditingController.text = address;
|
_textEditingController.text = address;
|
||||||
if (_urlRegex.hasMatch(address) && address.endsWith("/")) {
|
widget.onDone(address);
|
||||||
_textEditingController.text = address.replaceAll(RegExp(r'\/$'), '');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? _buildIsReachableIcon() {
|
String _replaceTrailingSlashes(String src) {
|
||||||
switch (_reachabilityStatus) {
|
return src.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||||
case ReachabilityStatus.reachable:
|
|
||||||
return const Icon(
|
|
||||||
Icons.done,
|
|
||||||
color: Colors.green,
|
|
||||||
);
|
|
||||||
case ReachabilityStatus.notReachable:
|
|
||||||
return Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
);
|
|
||||||
case ReachabilityStatus.testing:
|
|
||||||
return const RefreshProgressIndicator();
|
|
||||||
case ReachabilityStatus.undefined:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateIsAddressReachableStatus(String? address) async {
|
|
||||||
if (address == null || !_urlRegex.hasMatch(address)) {
|
|
||||||
setState(() {
|
|
||||||
_reachabilityStatus = ReachabilityStatus.undefined;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//https://stackoverflow.com/questions/49648022/check-whether-there-is-an-internet-connection-available-on-flutter-app
|
|
||||||
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
|
||||||
final isReachable = await context
|
|
||||||
.read<ConnectivityStatusService>()
|
|
||||||
.isServerReachable(address.trim());
|
|
||||||
setState(
|
|
||||||
() => _reachabilityStatus = isReachable
|
|
||||||
? ReachabilityStatus.reachable
|
|
||||||
: ReachabilityStatus.notReachable,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ReachabilityStatus { reachable, notReachable, testing, undefined }
|
|
||||||
|
|||||||
133
lib/features/login/view/widgets/server_connection_page.dart
Normal file
133
lib/features/login/view/widgets/server_connection_page.dart
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/view/widgets/client_certificate_form_field.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/view/widgets/server_address_form_field.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ServerConnectionPage extends StatefulWidget {
|
||||||
|
final GlobalKey<FormBuilderState> formBuilderKey;
|
||||||
|
final void Function() onContinue;
|
||||||
|
|
||||||
|
const ServerConnectionPage({
|
||||||
|
super.key,
|
||||||
|
required this.formBuilderKey,
|
||||||
|
required this.onContinue,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServerConnectionPage> createState() => _ServerConnectionPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
||||||
|
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.unknown;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final logoHeight = MediaQuery.of(context).size.width / 2;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(S.of(context).loginPageTitle),
|
||||||
|
),
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
ServerAddressFormField(
|
||||||
|
onDone: (address) {
|
||||||
|
_updateReachability();
|
||||||
|
},
|
||||||
|
).padded(),
|
||||||
|
ClientCertificateFormField(
|
||||||
|
onChanged: (_) => _updateReachability(),
|
||||||
|
).padded(),
|
||||||
|
_buildStatusIndicator(),
|
||||||
|
],
|
||||||
|
).padded(),
|
||||||
|
bottomNavigationBar: BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
child: Text("Continue"),
|
||||||
|
onPressed: _reachabilityStatus == ReachabilityStatus.reachable
|
||||||
|
? widget.onContinue
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateReachability() async {
|
||||||
|
final status = await context
|
||||||
|
.read<ConnectivityStatusService>()
|
||||||
|
.isPaperlessServerReachable(
|
||||||
|
widget.formBuilderKey.currentState!
|
||||||
|
.getRawValue(ServerAddressFormField.fkServerAddress),
|
||||||
|
widget.formBuilderKey.currentState?.getRawValue(
|
||||||
|
ClientCertificateFormField.fkClientCertificate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() => _reachabilityStatus = status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatusIndicator() {
|
||||||
|
Color errorColor = Theme.of(context).colorScheme.error;
|
||||||
|
switch (_reachabilityStatus) {
|
||||||
|
case ReachabilityStatus.unknown:
|
||||||
|
return Container();
|
||||||
|
case ReachabilityStatus.reachable:
|
||||||
|
return _buildIconText(
|
||||||
|
Icons.done,
|
||||||
|
"Connection established.",
|
||||||
|
Colors.green,
|
||||||
|
);
|
||||||
|
case ReachabilityStatus.notReachable:
|
||||||
|
return _buildIconText(
|
||||||
|
Icons.close,
|
||||||
|
"Could not establish a connection to the server.",
|
||||||
|
errorColor,
|
||||||
|
);
|
||||||
|
case ReachabilityStatus.unknownHost:
|
||||||
|
return _buildIconText(
|
||||||
|
Icons.close,
|
||||||
|
"Host could not be resolved.",
|
||||||
|
errorColor,
|
||||||
|
);
|
||||||
|
case ReachabilityStatus.missingClientCertificate:
|
||||||
|
return _buildIconText(
|
||||||
|
Icons.close,
|
||||||
|
"A client certificate was expected but not sent. Please provide a certificate.",
|
||||||
|
errorColor,
|
||||||
|
);
|
||||||
|
case ReachabilityStatus.invalidClientCertificateConfiguration:
|
||||||
|
return _buildIconText(
|
||||||
|
Icons.close,
|
||||||
|
"Incorrect or missing client certificate passphrase.",
|
||||||
|
errorColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIconText(
|
||||||
|
IconData icon,
|
||||||
|
String text, [
|
||||||
|
Color? color,
|
||||||
|
]) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
text,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: color),
|
||||||
|
),
|
||||||
|
leading: Icon(
|
||||||
|
icon,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
lib/features/login/view/widgets/server_login_page.dart
Normal file
49
lib/features/login/view/widgets/server_login_page.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/view/widgets/server_address_form_field.dart';
|
||||||
|
import 'package:paperless_mobile/features/login/view/widgets/user_credentials_form_field.dart';
|
||||||
|
|
||||||
|
class ServerLoginPage extends StatefulWidget {
|
||||||
|
final VoidCallback onDone;
|
||||||
|
final GlobalKey<FormBuilderState> formBuilderKey;
|
||||||
|
const ServerLoginPage({
|
||||||
|
super.key,
|
||||||
|
required this.onDone,
|
||||||
|
required this.formBuilderKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServerLoginPage> createState() => _ServerLoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerLoginPageState extends State<ServerLoginPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serverAddress = (widget.formBuilderKey.currentState
|
||||||
|
?.getRawValue(ServerAddressFormField.fkServerAddress) as String?)
|
||||||
|
?.replaceAll(RegExp(r'https?://'), '');
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text("Sign In"),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: [
|
||||||
|
Text("Sign in to $serverAddress").padded(),
|
||||||
|
UserCredentialsFormField(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: widget.onDone,
|
||||||
|
child: Text("Sign In"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
|||||||
StreamSubscription? _subscription;
|
StreamSubscription? _subscription;
|
||||||
|
|
||||||
SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
|
SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
|
||||||
_subscription = _repository.savedViews.listen(
|
_subscription = _repository.values.listen(
|
||||||
(savedViews) {
|
(savedViews) {
|
||||||
if (savedViews == null) {
|
if (savedViews == null) {
|
||||||
emit(state.copyWith(isLoaded: false));
|
emit(state.copyWith(isLoaded: false));
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
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:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
|
||||||
|
|
||||||
class DocumentScannerCubit extends Cubit<List<File>> {
|
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||||
DocumentScannerCubit() : super(const []);
|
DocumentScannerCubit() : super(const []);
|
||||||
|
|||||||
@@ -212,7 +212,7 @@
|
|||||||
"@errorMessageCreateSavedViewError": {},
|
"@errorMessageCreateSavedViewError": {},
|
||||||
"errorMessageDeleteSavedViewError": "Gespeicherte Ansicht konnte nicht geklöscht werden, bitte versuche es erneut.",
|
"errorMessageDeleteSavedViewError": "Gespeicherte Ansicht konnte nicht geklöscht werden, bitte versuche es erneut.",
|
||||||
"@errorMessageDeleteSavedViewError": {},
|
"@errorMessageDeleteSavedViewError": {},
|
||||||
"errorMessageDeviceOffline": "Daten konnten nicht geladen werden: Eine Verbindung zum Internet konnte nicht hergestellt werden.",
|
"errorMessageDeviceOffline": "Du bist offline. Bitte stelle sicher, dass du mit dem Internet verbunden bist.",
|
||||||
"@errorMessageDeviceOffline": {},
|
"@errorMessageDeviceOffline": {},
|
||||||
"errorMessageDocumentAsnQueryFailed": "Archiv-Seriennummer konnte nicht zugewiesen werden.",
|
"errorMessageDocumentAsnQueryFailed": "Archiv-Seriennummer konnte nicht zugewiesen werden.",
|
||||||
"@errorMessageDocumentAsnQueryFailed": {},
|
"@errorMessageDocumentAsnQueryFailed": {},
|
||||||
|
|||||||
@@ -212,7 +212,7 @@
|
|||||||
"@errorMessageCreateSavedViewError": {},
|
"@errorMessageCreateSavedViewError": {},
|
||||||
"errorMessageDeleteSavedViewError": "Could not delete saved view, please try again",
|
"errorMessageDeleteSavedViewError": "Could not delete saved view, please try again",
|
||||||
"@errorMessageDeleteSavedViewError": {},
|
"@errorMessageDeleteSavedViewError": {},
|
||||||
"errorMessageDeviceOffline": "Could not fetch data: You are not connected to the internet.",
|
"errorMessageDeviceOffline": "You are currently offline. Please make sure you are connected to the internet.",
|
||||||
"@errorMessageDeviceOffline": {},
|
"@errorMessageDeviceOffline": {},
|
||||||
"errorMessageDocumentAsnQueryFailed": "Could not assign archive serial number.",
|
"errorMessageDocumentAsnQueryFailed": "Could not assign archive serial number.",
|
||||||
"@errorMessageDocumentAsnQueryFailed": {},
|
"@errorMessageDocumentAsnQueryFailed": {},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||||
@@ -11,7 +10,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:intl/intl_standalone.dart';
|
import 'package:intl/intl_standalone.dart';
|
||||||
@@ -23,7 +21,6 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
|
|||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.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/language_header.interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
|
||||||
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
|
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
|
||||||
@@ -33,11 +30,10 @@ import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart';
|
|||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/core/security/authentication_aware_dio_manager.dart';
|
import 'package:paperless_mobile/core/security/authentication_aware_dio_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/service/dio_file_service.dart';
|
import 'package:paperless_mobile/core/service/dio_file_service.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||||
@@ -52,9 +48,9 @@ import 'package:paperless_mobile/features/settings/model/application_settings_st
|
|||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
Bloc.observer = BlocChangesObserver();
|
Bloc.observer = BlocChangesObserver();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class PaperlessServerException implements Exception {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "ErrorMessage(code: $code${stackTrace != null ? ', stackTrace: ${stackTrace.toString()}' : ''}${httpStatusCode != null ? ', httpStatusCode: $httpStatusCode' : ''})";
|
return "PaperlessServerException(code: $code${stackTrace != null ? ', stackTrace: ${stackTrace.toString()}' : ''}${httpStatusCode != null ? ', httpStatusCode: $httpStatusCode' : ''})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,8 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on DioError catch (error) {
|
} on DioError catch (error) {
|
||||||
if (error.error is ErrorCode) {
|
if (error.error is PaperlessServerException) {
|
||||||
throw PaperlessServerException(
|
throw error.error;
|
||||||
error.error,
|
|
||||||
httpStatusCode: error.response?.statusCode,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
log(error.message);
|
log(error.message);
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -42,19 +43,24 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
for (final tag in tags) {
|
for (final tag in tags) {
|
||||||
formData.fields.add(MapEntry('tags', tag.toString()));
|
formData.fields.add(MapEntry('tags', tag.toString()));
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
final response =
|
final response =
|
||||||
await client.post('/api/documents/post_document/', data: formData);
|
await client.post('/api/documents/post_document/', data: formData);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.documentUploadFailed,
|
ErrorCode.documentUploadFailed,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentModel> update(DocumentModel doc) async {
|
Future<DocumentModel> update(DocumentModel doc) async {
|
||||||
|
try {
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
"/api/documents/${doc.id}/",
|
"/api/documents/${doc.id}/",
|
||||||
data: doc.toJson(),
|
data: doc.toJson(),
|
||||||
@@ -64,11 +70,15 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
} else {
|
} else {
|
||||||
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
||||||
}
|
}
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PagedSearchResult<DocumentModel>> find(DocumentFilter filter) async {
|
Future<PagedSearchResult<DocumentModel>> find(DocumentFilter filter) async {
|
||||||
final filterParams = filter.toQueryParameters();
|
final filterParams = filter.toQueryParameters();
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
"/api/documents/",
|
"/api/documents/",
|
||||||
queryParameters: filterParams,
|
queryParameters: filterParams,
|
||||||
@@ -84,16 +94,23 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
} else {
|
} else {
|
||||||
throw const PaperlessServerException(ErrorCode.documentLoadFailed);
|
throw const PaperlessServerException(ErrorCode.documentLoadFailed);
|
||||||
}
|
}
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(DocumentModel doc) async {
|
Future<int> delete(DocumentModel doc) async {
|
||||||
|
try {
|
||||||
final response = await client.delete("/api/documents/${doc.id}/");
|
final response = await client.delete("/api/documents/${doc.id}/");
|
||||||
|
|
||||||
if (response.statusCode == 204) {
|
if (response.statusCode == 204) {
|
||||||
return Future.value(doc.id);
|
return Future.value(doc.id);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.documentDeleteFailed);
|
throw const PaperlessServerException(ErrorCode.documentDeleteFailed);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -107,16 +124,20 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> getPreview(int documentId) async {
|
Future<Uint8List> getPreview(int documentId) async {
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
getPreviewUrl(documentId),
|
getPreviewUrl(documentId),
|
||||||
options: Options(
|
options: Options(
|
||||||
responseType:
|
responseType: ResponseType
|
||||||
ResponseType.bytes), //TODO: Check if bytes or stream is required
|
.bytes), //TODO: Check if bytes or stream is required
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -134,13 +155,16 @@ 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 catch (_) {
|
} on PaperlessServerException {
|
||||||
throw const PaperlessServerException(ErrorCode.documentAsnQueryFailed);
|
throw const PaperlessServerException(ErrorCode.documentAsnQueryFailed);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
Future<Iterable<int>> bulkAction(BulkAction action) async {
|
||||||
|
try {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
data: action.toJson(),
|
data: action.toJson(),
|
||||||
@@ -148,7 +172,12 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return action.documentIds;
|
return action.documentIds;
|
||||||
} else {
|
} else {
|
||||||
throw const PaperlessServerException(ErrorCode.documentBulkActionFailed);
|
throw const PaperlessServerException(
|
||||||
|
ErrorCode.documentBulkActionFailed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,26 +203,34 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> download(DocumentModel document) async {
|
Future<Uint8List> download(DocumentModel document) async {
|
||||||
//TODO: Add missing error handling
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
"/api/documents/${document.id}/download/",
|
"/api/documents/${document.id}/download/",
|
||||||
options: Options(responseType: ResponseType.bytes),
|
options: Options(responseType: ResponseType.bytes),
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
||||||
|
try {
|
||||||
final response =
|
final response =
|
||||||
await client.get("/api/documents/${document.id}/metadata/");
|
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) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> autocomplete(String query, [int limit = 10]) async {
|
Future<List<String>> autocomplete(String query, [int limit = 10]) async {
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/api/search/autocomplete/',
|
'/api/search/autocomplete/',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -205,10 +242,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
return response.data as List<String>;
|
return response.data as List<String>;
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<SimilarDocumentModel>> findSimilar(int docId) async {
|
Future<List<SimilarDocumentModel>> findSimilar(int docId) async {
|
||||||
|
try {
|
||||||
final response =
|
final response =
|
||||||
await client.get("/api/documents/?more_like=$docId&pageSize=10");
|
await client.get("/api/documents/?more_like=$docId&pageSize=10");
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
@@ -222,5 +263,8 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
.results;
|
.results;
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.similarQueryError);
|
throw const PaperlessServerException(ErrorCode.similarQueryError);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ import 'package:paperless_api/src/request_utils.dart';
|
|||||||
//Notes:
|
//Notes:
|
||||||
// Removed content type json header
|
// Removed content type json header
|
||||||
class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||||
final Dio client;
|
final Dio _client;
|
||||||
|
|
||||||
PaperlessLabelApiImpl(this.client);
|
PaperlessLabelApiImpl(this._client);
|
||||||
@override
|
@override
|
||||||
Future<Correspondent?> getCorrespondent(int id) async {
|
Future<Correspondent?> getCorrespondent(int id) async {
|
||||||
return getSingleResult(
|
return getSingleResult(
|
||||||
"/api/correspondents/$id/",
|
"/api/correspondents/$id/",
|
||||||
Correspondent.fromJson,
|
Correspondent.fromJson,
|
||||||
ErrorCode.correspondentLoadFailed,
|
ErrorCode.correspondentLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/tags/$id/",
|
"/api/tags/$id/",
|
||||||
Tag.fromJson,
|
Tag.fromJson,
|
||||||
ErrorCode.tagLoadFailed,
|
ErrorCode.tagLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/tags/?page=1&page_size=100000",
|
"/api/tags/?page=1&page_size=100000",
|
||||||
Tag.fromJson,
|
Tag.fromJson,
|
||||||
ErrorCode.tagLoadFailed,
|
ErrorCode.tagLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
minRequiredApiVersion: 2,
|
minRequiredApiVersion: 2,
|
||||||
);
|
);
|
||||||
return results
|
return results
|
||||||
@@ -56,7 +56,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/document_types/$id/",
|
"/api/document_types/$id/",
|
||||||
DocumentType.fromJson,
|
DocumentType.fromJson,
|
||||||
ErrorCode.documentTypeLoadFailed,
|
ErrorCode.documentTypeLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/correspondents/?page=1&page_size=100000",
|
"/api/correspondents/?page=1&page_size=100000",
|
||||||
Correspondent.fromJson,
|
Correspondent.fromJson,
|
||||||
ErrorCode.correspondentLoadFailed,
|
ErrorCode.correspondentLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -80,7 +80,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/document_types/?page=1&page_size=100000",
|
"/api/document_types/?page=1&page_size=100000",
|
||||||
DocumentType.fromJson,
|
DocumentType.fromJson,
|
||||||
ErrorCode.documentTypeLoadFailed,
|
ErrorCode.documentTypeLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -90,7 +90,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Correspondent> saveCorrespondent(Correspondent correspondent) async {
|
Future<Correspondent> saveCorrespondent(Correspondent correspondent) async {
|
||||||
final response = await client.post(
|
try {
|
||||||
|
final response = await _client.post(
|
||||||
'/api/correspondents/',
|
'/api/correspondents/',
|
||||||
data: correspondent.toJson(),
|
data: correspondent.toJson(),
|
||||||
);
|
);
|
||||||
@@ -101,11 +102,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.correspondentCreateFailed,
|
ErrorCode.correspondentCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentType> saveDocumentType(DocumentType type) async {
|
Future<DocumentType> saveDocumentType(DocumentType type) async {
|
||||||
final response = await client.post(
|
try {
|
||||||
|
final response = await _client.post(
|
||||||
'/api/document_types/',
|
'/api/document_types/',
|
||||||
data: type.toJson(),
|
data: type.toJson(),
|
||||||
);
|
);
|
||||||
@@ -116,11 +121,15 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.documentTypeCreateFailed,
|
ErrorCode.documentTypeCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Tag> saveTag(Tag tag) async {
|
Future<Tag> saveTag(Tag tag) async {
|
||||||
final response = await client.post(
|
try {
|
||||||
|
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"}),
|
||||||
@@ -132,13 +141,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.tagCreateFailed,
|
ErrorCode.tagCreateFailed,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
||||||
assert(correspondent.id != null);
|
assert(correspondent.id != null);
|
||||||
|
try {
|
||||||
final response =
|
final response =
|
||||||
await client.delete('/api/correspondents/${correspondent.id}/');
|
await _client.delete('/api/correspondents/${correspondent.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return correspondent.id!;
|
return correspondent.id!;
|
||||||
}
|
}
|
||||||
@@ -146,13 +159,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> deleteDocumentType(DocumentType documentType) async {
|
Future<int> deleteDocumentType(DocumentType documentType) async {
|
||||||
assert(documentType.id != null);
|
assert(documentType.id != null);
|
||||||
|
try {
|
||||||
final response =
|
final response =
|
||||||
await client.delete('/api/document_types/${documentType.id}/');
|
await _client.delete('/api/document_types/${documentType.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return documentType.id!;
|
return documentType.id!;
|
||||||
}
|
}
|
||||||
@@ -160,12 +177,16 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> deleteTag(Tag tag) async {
|
Future<int> deleteTag(Tag tag) async {
|
||||||
assert(tag.id != null);
|
assert(tag.id != null);
|
||||||
final response = await client.delete('/api/tags/${tag.id}/');
|
try {
|
||||||
|
final response = await _client.delete('/api/tags/${tag.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return tag.id!;
|
return tag.id!;
|
||||||
}
|
}
|
||||||
@@ -173,12 +194,16 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
||||||
assert(correspondent.id != null);
|
assert(correspondent.id != null);
|
||||||
final response = await client.put(
|
try {
|
||||||
|
final response = await _client.put(
|
||||||
'/api/correspondents/${correspondent.id}/',
|
'/api/correspondents/${correspondent.id}/',
|
||||||
data: json.encode(correspondent.toJson()),
|
data: json.encode(correspondent.toJson()),
|
||||||
);
|
);
|
||||||
@@ -189,12 +214,16 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown, //TODO: Add correct error code mapping.
|
ErrorCode.unknown, //TODO: Add correct error code mapping.
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
||||||
assert(documentType.id != null);
|
assert(documentType.id != null);
|
||||||
final response = await client.put(
|
try {
|
||||||
|
final response = await _client.put(
|
||||||
'/api/document_types/${documentType.id}/',
|
'/api/document_types/${documentType.id}/',
|
||||||
data: documentType.toJson(),
|
data: documentType.toJson(),
|
||||||
);
|
);
|
||||||
@@ -205,12 +234,16 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Tag> updateTag(Tag tag) async {
|
Future<Tag> updateTag(Tag tag) async {
|
||||||
assert(tag.id != null);
|
assert(tag.id != null);
|
||||||
final response = await client.put(
|
try {
|
||||||
|
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"}),
|
||||||
data: tag.toJson(),
|
data: tag.toJson(),
|
||||||
@@ -222,12 +255,16 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> deleteStoragePath(StoragePath path) async {
|
Future<int> deleteStoragePath(StoragePath path) async {
|
||||||
assert(path.id != null);
|
assert(path.id != null);
|
||||||
final response = await client.delete('/api/storage_paths/${path.id}/');
|
try {
|
||||||
|
final response = await _client.delete('/api/storage_paths/${path.id}/');
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return path.id!;
|
return path.id!;
|
||||||
}
|
}
|
||||||
@@ -235,6 +272,9 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
ErrorCode.unknown,
|
ErrorCode.unknown,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -243,7 +283,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/storage_paths/$id/",
|
"/api/storage_paths/$id/",
|
||||||
StoragePath.fromJson,
|
StoragePath.fromJson,
|
||||||
ErrorCode.storagePathLoadFailed,
|
ErrorCode.storagePathLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +293,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
"/api/storage_paths/?page=1&page_size=100000",
|
"/api/storage_paths/?page=1&page_size=100000",
|
||||||
StoragePath.fromJson,
|
StoragePath.fromJson,
|
||||||
ErrorCode.storagePathLoadFailed,
|
ErrorCode.storagePathLoadFailed,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -263,21 +303,28 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StoragePath> saveStoragePath(StoragePath path) async {
|
Future<StoragePath> saveStoragePath(StoragePath path) async {
|
||||||
final response = await client.post(
|
try {
|
||||||
|
final response = await _client.post(
|
||||||
'/api/storage_paths/',
|
'/api/storage_paths/',
|
||||||
data: path.toJson(),
|
data: path.toJson(),
|
||||||
);
|
);
|
||||||
if (response.statusCode == HttpStatus.created) {
|
if (response.statusCode == HttpStatus.created) {
|
||||||
return StoragePath.fromJson(response.data);
|
return StoragePath.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw PaperlessServerException(ErrorCode.storagePathCreateFailed,
|
throw PaperlessServerException(
|
||||||
httpStatusCode: response.statusCode);
|
ErrorCode.storagePathCreateFailed,
|
||||||
|
httpStatusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StoragePath> updateStoragePath(StoragePath path) async {
|
Future<StoragePath> updateStoragePath(StoragePath path) async {
|
||||||
assert(path.id != null);
|
assert(path.id != null);
|
||||||
final response = await client.put(
|
try {
|
||||||
|
final response = await _client.put(
|
||||||
'/api/storage_paths/${path.id}/',
|
'/api/storage_paths/${path.id}/',
|
||||||
data: path.toJson(),
|
data: path.toJson(),
|
||||||
);
|
);
|
||||||
@@ -285,5 +332,8 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
return StoragePath.fromJson(response.data);
|
return StoragePath.fromJson(response.data);
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.unknown);
|
throw const PaperlessServerException(ErrorCode.unknown);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -9,9 +8,9 @@ import 'package:paperless_api/src/request_utils.dart';
|
|||||||
import 'paperless_saved_views_api.dart';
|
import 'paperless_saved_views_api.dart';
|
||||||
|
|
||||||
class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||||
final Dio client;
|
final Dio _client;
|
||||||
|
|
||||||
PaperlessSavedViewsApiImpl(this.client);
|
PaperlessSavedViewsApiImpl(this._client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||||
@@ -19,7 +18,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
"/api/saved_views/",
|
"/api/saved_views/",
|
||||||
SavedView.fromJson,
|
SavedView.fromJson,
|
||||||
ErrorCode.loadSavedViewsError,
|
ErrorCode.loadSavedViewsError,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.where((view) => ids?.contains(view.id!) ?? true);
|
return result.where((view) => ids?.contains(view.id!) ?? true);
|
||||||
@@ -27,7 +26,8 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SavedView> save(SavedView view) async {
|
Future<SavedView> save(SavedView view) async {
|
||||||
final response = await client.post(
|
try {
|
||||||
|
final response = await _client.post(
|
||||||
"/api/saved_views/",
|
"/api/saved_views/",
|
||||||
data: view.toJson(),
|
data: view.toJson(),
|
||||||
);
|
);
|
||||||
@@ -38,11 +38,15 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
ErrorCode.createSavedViewError,
|
ErrorCode.createSavedViewError,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> delete(SavedView view) async {
|
Future<int> delete(SavedView view) async {
|
||||||
final response = await client.delete("/api/saved_views/${view.id}/");
|
try {
|
||||||
|
final response = await _client.delete("/api/saved_views/${view.id}/");
|
||||||
if (response.statusCode == HttpStatus.noContent) {
|
if (response.statusCode == HttpStatus.noContent) {
|
||||||
return view.id!;
|
return view.id!;
|
||||||
}
|
}
|
||||||
@@ -50,6 +54,9 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
ErrorCode.deleteSavedViewError,
|
ErrorCode.deleteSavedViewError,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -58,7 +65,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
"/api/saved_views/$id/",
|
"/api/saved_views/$id/",
|
||||||
SavedView.fromJson,
|
SavedView.fromJson,
|
||||||
ErrorCode.loadSavedViewsError,
|
ErrorCode.loadSavedViewsError,
|
||||||
client: client,
|
client: _client,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Future<T> getSingleResult<T>(
|
|||||||
required Dio client,
|
required Dio client,
|
||||||
int minRequiredApiVersion = 1,
|
int minRequiredApiVersion = 1,
|
||||||
}) async {
|
}) async {
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
url,
|
url,
|
||||||
options: Options(
|
options: Options(
|
||||||
@@ -27,6 +28,9 @@ Future<T> getSingleResult<T>(
|
|||||||
errorCode,
|
errorCode,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<T>> getCollection<T>(
|
Future<List<T>> getCollection<T>(
|
||||||
@@ -36,6 +40,7 @@ Future<List<T>> getCollection<T>(
|
|||||||
required Dio client,
|
required Dio client,
|
||||||
int minRequiredApiVersion = 1,
|
int minRequiredApiVersion = 1,
|
||||||
}) async {
|
}) async {
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
url,
|
url,
|
||||||
options: Options(headers: {
|
options: Options(headers: {
|
||||||
@@ -50,8 +55,8 @@ Future<List<T>> getCollection<T>(
|
|||||||
} else {
|
} else {
|
||||||
return compute(
|
return compute(
|
||||||
_collectionFromJson,
|
_collectionFromJson,
|
||||||
_CollectionFromJsonSerializationParams(
|
_CollectionFromJsonSerializationParams(fromJson,
|
||||||
fromJson, (body['results'] as List).cast<Map<String, dynamic>>()),
|
(body['results'] as List).cast<Map<String, dynamic>>()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +65,9 @@ Future<List<T>> getCollection<T>(
|
|||||||
errorCode,
|
errorCode,
|
||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
|
} on DioError catch (err) {
|
||||||
|
throw err.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<T> _collectionFromJson<T>(
|
List<T> _collectionFromJson<T>(
|
||||||
|
|||||||
Reference in New Issue
Block a user