mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 16:07:57 -06:00
WIP - Redesigned login flow
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
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> {
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
@@ -9,12 +11,22 @@ class DioHttpErrorInterceptor extends Interceptor {
|
||||
// try to parse contained error message, otherwise return response
|
||||
final dynamic data = err.response?.data;
|
||||
if (data is Map<String, dynamic>) {
|
||||
return _handlePaperlessValidationError(data, handler, err);
|
||||
_handlePaperlessValidationError(data, handler, err);
|
||||
} 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(
|
||||
@@ -54,7 +66,8 @@ class DioHttpErrorInterceptor extends Interceptor {
|
||||
DioError(
|
||||
requestOptions: err.requestOptions,
|
||||
type: DioErrorType.response,
|
||||
error: ErrorCode.missingClientCertificate,
|
||||
error: const PaperlessServerException(
|
||||
ErrorCode.missingClientCertificate),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
|
||||
class LanguageHeaderInterceptor extends Interceptor {
|
||||
String preferredLocaleSubtag;
|
||||
|
||||
@@ -15,6 +15,7 @@ class RetryOnConnectionChangeInterceptor extends Interceptor {
|
||||
try {
|
||||
handler.resolve(await DioHttpRequestRetrier(dio: dio)
|
||||
.requestRetry(err.requestOptions)
|
||||
// ignore: body_might_complete_normally_catch_error
|
||||
.catchError((e) {
|
||||
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> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, Correspondent>>.seeded(const {});
|
||||
final _subject = BehaviorSubject<Map<int, Correspondent>?>();
|
||||
|
||||
CorrespondentRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, Correspondent>> get labels =>
|
||||
bool get isInitialized => _subject.valueOrNull != null;
|
||||
|
||||
@override
|
||||
Stream<Map<int, Correspondent>?> get values =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
Map<int, Correspondent> get _currentValueOrEmpty =>
|
||||
_subject.valueOrNull ?? {};
|
||||
|
||||
@override
|
||||
Future<Correspondent> create(Correspondent correspondent) async {
|
||||
final created = await _api.saveCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(Correspondent correspondent) async {
|
||||
Future<int> delete(Correspondent correspondent) async {
|
||||
await _api.deleteCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..removeWhere((k, v) => k == correspondent.id);
|
||||
_subject.add(updatedState);
|
||||
return correspondent.id!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Correspondent?> find(int id) async {
|
||||
final correspondent = await _api.getCorrespondent(id);
|
||||
if (correspondent != null) {
|
||||
final updatedState = {..._subject.value}..[id] = correspondent;
|
||||
final updatedState = {..._currentValueOrEmpty}..[id] = correspondent;
|
||||
_subject.add(updatedState);
|
||||
return correspondent;
|
||||
}
|
||||
@@ -45,7 +53,7 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||
@override
|
||||
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
||||
final correspondents = await _api.getCorrespondents(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return correspondents;
|
||||
@@ -54,7 +62,7 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||
@override
|
||||
Future<Correspondent> update(Correspondent correspondent) async {
|
||||
final updated = await _api.updateCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
@@ -62,9 +70,9 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
_subject.add(null);
|
||||
}
|
||||
|
||||
@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> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, DocumentType>>.seeded(const {});
|
||||
final _subject = BehaviorSubject<Map<int, DocumentType>?>();
|
||||
|
||||
DocumentTypeRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, DocumentType>> get labels =>
|
||||
Stream<Map<int, DocumentType>?> get values =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
bool get isInitialized => _subject.valueOrNull != null;
|
||||
|
||||
Map<int, DocumentType> get _currentValueOrEmpty => _subject.valueOrNull ?? {};
|
||||
|
||||
@override
|
||||
Future<DocumentType> create(DocumentType documentType) async {
|
||||
final created = await _api.saveDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(DocumentType documentType) async {
|
||||
Future<int> delete(DocumentType documentType) async {
|
||||
await _api.deleteDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..removeWhere((k, v) => k == documentType.id);
|
||||
_subject.add(updatedState);
|
||||
return documentType.id!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentType?> find(int id) async {
|
||||
final documentType = await _api.getDocumentType(id);
|
||||
if (documentType != null) {
|
||||
final updatedState = {..._subject.value}..[id] = documentType;
|
||||
final updatedState = {..._currentValueOrEmpty}..[id] = documentType;
|
||||
_subject.add(updatedState);
|
||||
return documentType;
|
||||
}
|
||||
@@ -44,7 +50,7 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||
@override
|
||||
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
||||
final documentTypes = await _api.getDocumentTypes(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return documentTypes;
|
||||
@@ -53,7 +59,7 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||
@override
|
||||
Future<DocumentType> update(DocumentType documentType) async {
|
||||
final updated = await _api.updateDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
@@ -65,5 +71,5 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
final BehaviorSubject<Map<int, SavedView>?> _subject = BehaviorSubject();
|
||||
final _subject = BehaviorSubject<Map<int, SavedView>?>();
|
||||
|
||||
@override
|
||||
Stream<Map<int, SavedView>?> get savedViews =>
|
||||
Stream<Map<int, SavedView>?> get values =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
@@ -54,4 +54,15 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
_subject.add(updatedState);
|
||||
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> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, StoragePath>>.seeded(const {});
|
||||
final _subject = BehaviorSubject<Map<int, StoragePath>?>();
|
||||
|
||||
StoragePathRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, StoragePath>> get labels =>
|
||||
Stream<Map<int, StoragePath>?> get values =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
Map<int, StoragePath> get _currentValueOrEmpty => _subject.valueOrNull ?? {};
|
||||
|
||||
@override
|
||||
Future<StoragePath> create(StoragePath storagePath) async {
|
||||
final created = await _api.saveStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(StoragePath storagePath) async {
|
||||
Future<int> delete(StoragePath storagePath) async {
|
||||
await _api.deleteStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..removeWhere((k, v) => k == storagePath.id);
|
||||
_subject.add(updatedState);
|
||||
return storagePath.id!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StoragePath?> find(int id) async {
|
||||
final storagePath = await _api.getStoragePath(id);
|
||||
if (storagePath != null) {
|
||||
final updatedState = {..._subject.value}..[id] = storagePath;
|
||||
final updatedState = {..._currentValueOrEmpty}..[id] = storagePath;
|
||||
_subject.add(updatedState);
|
||||
return storagePath;
|
||||
}
|
||||
@@ -44,7 +47,7 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||
@override
|
||||
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
||||
final storagePaths = await _api.getStoragePaths(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return storagePaths;
|
||||
@@ -53,7 +56,7 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||
@override
|
||||
Future<StoragePath> update(StoragePath storagePath) async {
|
||||
final updated = await _api.updateStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
@@ -65,5 +68,8 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||
}
|
||||
|
||||
@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> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, Tag>>.seeded(const {});
|
||||
final _subject = BehaviorSubject<Map<int, Tag>?>();
|
||||
|
||||
TagRepositoryImpl(this._api);
|
||||
|
||||
@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
|
||||
Future<Tag> create(Tag tag) async {
|
||||
final created = await _api.saveTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(Tag tag) async {
|
||||
Future<int> delete(Tag tag) async {
|
||||
await _api.deleteTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..removeWhere((k, v) => k == tag.id);
|
||||
_subject.add(updatedState);
|
||||
return tag.id!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Tag?> find(int id) async {
|
||||
final tag = await _api.getTag(id);
|
||||
if (tag != null) {
|
||||
final updatedState = {..._subject.value}..[id] = tag;
|
||||
final updatedState = {..._currentValueOrEmpty}..[id] = tag;
|
||||
_subject.add(updatedState);
|
||||
return tag;
|
||||
}
|
||||
@@ -43,7 +46,7 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||
@override
|
||||
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
||||
final tags = await _api.getTags(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return tags;
|
||||
@@ -52,7 +55,7 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||
@override
|
||||
Future<Tag> update(Tag tag) async {
|
||||
final updated = await _api.updateTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._currentValueOrEmpty}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
@@ -60,9 +63,12 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
_subject.add(null);
|
||||
}
|
||||
|
||||
@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_mobile/core/repository/base_repository.dart';
|
||||
|
||||
abstract class LabelRepository<T extends Label> {
|
||||
Stream<Map<int, T>> get labels;
|
||||
|
||||
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();
|
||||
}
|
||||
abstract class LabelRepository<T extends Label>
|
||||
implements BaseRepository<Map<int, T>, T> {}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
||||
|
||||
abstract class SavedViewRepository {
|
||||
Stream<Map<int, SavedView>?> get savedViews;
|
||||
|
||||
Future<SavedView> create(SavedView view);
|
||||
Future<SavedView?> find(int id);
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]);
|
||||
Future<int> delete(SavedView view);
|
||||
|
||||
void clear();
|
||||
}
|
||||
abstract class SavedViewRepository
|
||||
implements BaseRepository<Map<int, SavedView>, SavedView> {}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/adapter.dart';
|
||||
import 'package:dio/dio.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';
|
||||
|
||||
class AuthenticationAwareDioManager {
|
||||
final Dio client;
|
||||
final List<Interceptor> interceptors;
|
||||
|
||||
/// Some dependencies require an [HttpClient], therefore this is also maintained here.
|
||||
|
||||
AuthenticationAwareDioManager([this.interceptors = const []])
|
||||
: 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:form_builder_validators/form_builder_validators.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';
|
||||
|
||||
class FormBuilderRelativeDateRangePicker extends StatefulWidget {
|
||||
|
||||
@@ -13,7 +13,7 @@ class OfflineBanner extends StatelessWidget with PreferredSizeWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.cloud_off,
|
||||
size: 24,
|
||||
|
||||
@@ -4,7 +4,16 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
class PaperlessLogo extends StatelessWidget {
|
||||
final double? height;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
@@ -15,8 +24,7 @@ class PaperlessLogo extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: SvgPicture.asset(
|
||||
"assets/logo/paperless_ng_logo_light.svg",
|
||||
color: Theme.of(context).primaryColor,
|
||||
_path,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user