mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 16:07:58 -06:00
feat: Add debug output, run app in guarded zone
This commit is contained in:
@@ -19,11 +19,15 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
} else if (err.response?.statusCode == 403) {
|
} else if (err.response?.statusCode == 403) {
|
||||||
var data = err.response!.data;
|
var data = err.response!.data;
|
||||||
if (data is Map && data.containsKey("detail")) {
|
if (data is Map && data.containsKey("detail")) {
|
||||||
handler.reject(DioError(
|
handler.reject(
|
||||||
requestOptions: err.requestOptions,
|
DioError(
|
||||||
error: ServerMessageException(data['detail']),
|
message: data['detail'],
|
||||||
response: err.response,
|
requestOptions: err.requestOptions,
|
||||||
));
|
error: ServerMessageException(data['detail']),
|
||||||
|
response: err.response,
|
||||||
|
type: DioErrorType.unknown,
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (err.error is SocketException) {
|
} else if (err.error is SocketException) {
|
||||||
@@ -31,6 +35,7 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
if (ex.osError?.errorCode == _OsErrorCodes.serverUnreachable.code) {
|
if (ex.osError?.errorCode == _OsErrorCodes.serverUnreachable.code) {
|
||||||
return handler.reject(
|
return handler.reject(
|
||||||
DioError(
|
DioError(
|
||||||
|
message: "The server could not be reached. Is the device offline?",
|
||||||
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.connectionTimeout,
|
type: DioErrorType.connectionTimeout,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
@@ -22,13 +23,14 @@ class SessionManager extends ValueNotifier<Dio> {
|
|||||||
BaseOptions(contentType: Headers.jsonContentType),
|
BaseOptions(contentType: Headers.jsonContentType),
|
||||||
);
|
);
|
||||||
dio.options
|
dio.options
|
||||||
..receiveTimeout = const Duration(seconds: 20)
|
..receiveTimeout = const Duration(seconds: 30)
|
||||||
..sendTimeout = const Duration(seconds: 60)
|
..sendTimeout = const Duration(seconds: 60)
|
||||||
..responseType = ResponseType.json;
|
..responseType = ResponseType.json;
|
||||||
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
||||||
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
||||||
dio.interceptors.addAll([
|
dio.interceptors.addAll([
|
||||||
...interceptors,
|
...interceptors,
|
||||||
|
DioHttpErrorInterceptor(),
|
||||||
PrettyDioLogger(
|
PrettyDioLogger(
|
||||||
compact: true,
|
compact: true,
|
||||||
responseBody: false,
|
responseBody: false,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
|||||||
@@ -130,10 +130,17 @@ class HomeRoute extends StatelessWidget {
|
|||||||
.get(currentLocalUserId)!,
|
.get(currentLocalUserId)!,
|
||||||
)..initialize(),
|
)..initialize(),
|
||||||
),
|
),
|
||||||
Provider(create: (context) => DocumentScannerCubit(context.read())),
|
Provider(
|
||||||
ProxyProvider4<PaperlessDocumentsApi, PaperlessServerStatsApi, LabelRepository,
|
create: (context) =>
|
||||||
DocumentChangedNotifier, InboxCubit>(
|
DocumentScannerCubit(context.read())),
|
||||||
update: (context, docApi, statsApi, labelRepo, notifier, previous) =>
|
ProxyProvider4<
|
||||||
|
PaperlessDocumentsApi,
|
||||||
|
PaperlessServerStatsApi,
|
||||||
|
LabelRepository,
|
||||||
|
DocumentChangedNotifier,
|
||||||
|
InboxCubit>(
|
||||||
|
update: (context, docApi, statsApi, labelRepo, notifier,
|
||||||
|
previous) =>
|
||||||
InboxCubit(
|
InboxCubit(
|
||||||
docApi,
|
docApi,
|
||||||
statsApi,
|
statsApi,
|
||||||
@@ -143,9 +150,7 @@ class HomeRoute extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ProxyProvider<SavedViewRepository, SavedViewCubit>(
|
ProxyProvider<SavedViewRepository, SavedViewCubit>(
|
||||||
update: (context, savedViewRepo, previous) =>
|
update: (context, savedViewRepo, previous) =>
|
||||||
SavedViewCubit(
|
SavedViewCubit(savedViewRepo),
|
||||||
savedViewRepo,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
ProxyProvider<LabelRepository, LabelCubit>(
|
ProxyProvider<LabelRepository, LabelCubit>(
|
||||||
update: (context, value, previous) => LabelCubit(value),
|
update: (context, value, previous) => LabelCubit(value),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
@@ -37,7 +38,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
}) async {
|
}) async {
|
||||||
assert(credentials.username != null && credentials.password != null);
|
assert(credentials.username != null && credentials.password != null);
|
||||||
final localUserId = "${credentials.username}@$serverUrl";
|
final localUserId = "${credentials.username}@$serverUrl";
|
||||||
|
_debugPrintMessage(
|
||||||
|
"login",
|
||||||
|
"Trying to login $localUserId...",
|
||||||
|
);
|
||||||
await _addUser(
|
await _addUser(
|
||||||
localUserId,
|
localUserId,
|
||||||
serverUrl,
|
serverUrl,
|
||||||
@@ -60,6 +64,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
localUserId: localUserId,
|
localUserId: localUserId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"login",
|
||||||
|
"User successfully logged in.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switches to another account if it exists.
|
/// Switches to another account if it exists.
|
||||||
@@ -161,26 +169,57 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
/// Performs a conditional hydration based on the local authentication success.
|
/// Performs a conditional hydration based on the local authentication success.
|
||||||
///
|
///
|
||||||
Future<void> restoreSessionState() async {
|
Future<void> restoreSessionState() async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Trying to restore previous session...",
|
||||||
|
);
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
final localUserId = globalSettings.currentLoggedInUser;
|
final localUserId = globalSettings.currentLoggedInUser;
|
||||||
if (localUserId == null) {
|
if (localUserId == null) {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"There is nothing to restore.",
|
||||||
|
);
|
||||||
// If there is nothing to restore, we can quit here.
|
// If there is nothing to restore, we can quit here.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final localUserAccountBox =
|
final localUserAccountBox =
|
||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
final localUserAccount = localUserAccountBox.get(localUserId)!;
|
final localUserAccount = localUserAccountBox.get(localUserId)!;
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Checking if biometric authentication is required...",
|
||||||
|
);
|
||||||
if (localUserAccount.settings.isBiometricAuthenticationEnabled) {
|
if (localUserAccount.settings.isBiometricAuthenticationEnabled) {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Biometric authentication required, waiting for user to authenticate...",
|
||||||
|
);
|
||||||
final localAuthSuccess = await _localAuthService
|
final localAuthSuccess = await _localAuthService
|
||||||
.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
|
.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
|
||||||
if (!localAuthSuccess) {
|
if (!localAuthSuccess) {
|
||||||
emit(const AuthenticationState.requriresLocalAuthentication());
|
emit(const AuthenticationState.requriresLocalAuthentication());
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"User could not be authenticated.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"User successfully autheticated.",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Biometric authentication not configured, skipping.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Trying to retrieve authentication credentials...",
|
||||||
|
);
|
||||||
final authentication =
|
final authentication =
|
||||||
await withEncryptedBox<UserCredentials, UserCredentials>(
|
await withEncryptedBox<UserCredentials, UserCredentials>(
|
||||||
HiveBoxes.localUserCredentials, (box) {
|
HiveBoxes.localUserCredentials, (box) {
|
||||||
@@ -188,14 +227,32 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Could not retrieve existing authentication credentials.",
|
||||||
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"User should be authenticated but no authentication information was found."); //TODO: INTL
|
"User should be authenticated but no authentication information was found.",
|
||||||
|
); //TODO: INTL
|
||||||
}
|
}
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Authentication credentials successfully retrieved.",
|
||||||
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Updating current session state...",
|
||||||
|
);
|
||||||
_sessionManager.updateSettings(
|
_sessionManager.updateSettings(
|
||||||
clientCertificate: authentication.clientCertificate,
|
clientCertificate: authentication.clientCertificate,
|
||||||
authToken: authentication.token,
|
authToken: authentication.token,
|
||||||
baseUrl: localUserAccount.serverUrl,
|
baseUrl: localUserAccount.serverUrl,
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Current session state successfully updated.",
|
||||||
|
);
|
||||||
|
|
||||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||||
await _updateRemoteUser(
|
await _updateRemoteUser(
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
@@ -208,20 +265,41 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
localUserId: localUserId,
|
localUserId: localUserId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"restoreSessionState",
|
||||||
|
"Session was successfully restored.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"logout",
|
||||||
|
"Trying to log out current user...",
|
||||||
|
);
|
||||||
await _resetExternalState();
|
await _resetExternalState();
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
globalSettings.currentLoggedInUser = null;
|
globalSettings.currentLoggedInUser = null;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
|
|
||||||
emit(const AuthenticationState.unauthenticated());
|
emit(const AuthenticationState.unauthenticated());
|
||||||
|
_debugPrintMessage(
|
||||||
|
"logout",
|
||||||
|
"User successfully logged out.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resetExternalState() async {
|
Future<void> _resetExternalState() async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_resetExternalState",
|
||||||
|
"Resetting session manager and clearing storage...",
|
||||||
|
);
|
||||||
_sessionManager.resetSettings();
|
_sessionManager.resetSettings();
|
||||||
await HydratedBloc.storage.clear();
|
await HydratedBloc.storage.clear();
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_resetExternalState",
|
||||||
|
"Session manager successfully reset and storage cleared.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _addUser(
|
Future<int> _addUser(
|
||||||
@@ -232,6 +310,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
SessionManager sessionManager,
|
SessionManager sessionManager,
|
||||||
) async {
|
) async {
|
||||||
assert(credentials.username != null && credentials.password != null);
|
assert(credentials.username != null && credentials.password != null);
|
||||||
|
_debugPrintMessage("_addUser", "Adding new user $localUserId...");
|
||||||
|
|
||||||
sessionManager.updateSettings(
|
sessionManager.updateSettings(
|
||||||
baseUrl: serverUrl,
|
baseUrl: serverUrl,
|
||||||
@@ -240,11 +319,21 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
|
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
|
||||||
|
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Trying to login user ${credentials.username} on $serverUrl...",
|
||||||
|
);
|
||||||
|
|
||||||
final token = await authApi.login(
|
final token = await authApi.login(
|
||||||
username: credentials.username!,
|
username: credentials.username!,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Successfully acquired token.",
|
||||||
|
);
|
||||||
|
|
||||||
sessionManager.updateSettings(
|
sessionManager.updateSettings(
|
||||||
baseUrl: serverUrl,
|
baseUrl: serverUrl,
|
||||||
clientCertificate: clientCert,
|
clientCertificate: clientCert,
|
||||||
@@ -257,17 +346,41 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||||
|
|
||||||
if (userAccountBox.containsKey(localUserId)) {
|
if (userAccountBox.containsKey(localUserId)) {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"An error occurred! The user $localUserId already exists.",
|
||||||
|
);
|
||||||
throw Exception("User already exists!");
|
throw Exception("User already exists!");
|
||||||
}
|
}
|
||||||
final apiVersion = await _getApiVersion(sessionManager.client);
|
final apiVersion = await _getApiVersion(sessionManager.client);
|
||||||
|
_debugPrintMessage(
|
||||||
final serverUser = await _apiFactory
|
"_addUser",
|
||||||
.createUserApi(
|
"Trying to fetch user object for $localUserId...",
|
||||||
sessionManager.client,
|
);
|
||||||
apiVersion: apiVersion,
|
late UserModel serverUser;
|
||||||
)
|
try {
|
||||||
.findCurrentUser();
|
serverUser = await _apiFactory
|
||||||
|
.createUserApi(
|
||||||
|
sessionManager.client,
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
)
|
||||||
|
.findCurrentUser();
|
||||||
|
} on DioError catch (error, stackTrace) {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"An error occurred: ${error.message}",
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"User object successfully fetched.",
|
||||||
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Persisting local user account...",
|
||||||
|
);
|
||||||
// Create user account
|
// Create user account
|
||||||
await userAccountBox.put(
|
await userAccountBox.put(
|
||||||
localUserId,
|
localUserId,
|
||||||
@@ -278,15 +391,29 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
paperlessUser: serverUser,
|
paperlessUser: serverUser,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Local user account successfully persisted.",
|
||||||
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Persisting user state...",
|
||||||
|
);
|
||||||
// Create user state
|
// Create user state
|
||||||
await userStateBox.put(
|
await userStateBox.put(
|
||||||
localUserId,
|
localUserId,
|
||||||
LocalUserAppState(userId: localUserId),
|
LocalUserAppState(userId: localUserId),
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"User state successfully persisted.",
|
||||||
|
);
|
||||||
// Save credentials in encrypted box
|
// Save credentials in encrypted box
|
||||||
await withEncryptedBox(HiveBoxes.localUserCredentials, (box) async {
|
await withEncryptedBox(HiveBoxes.localUserCredentials, (box) async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"Saving user credentials inside encrypted storage...",
|
||||||
|
);
|
||||||
await box.put(
|
await box.put(
|
||||||
localUserId,
|
localUserId,
|
||||||
UserCredentials(
|
UserCredentials(
|
||||||
@@ -294,17 +421,32 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
clientCertificate: clientCert,
|
clientCertificate: clientCert,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_addUser",
|
||||||
|
"User credentials successfully saved.",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
final hostsBox = Hive.box<String>(HiveBoxes.hosts);
|
final hostsBox = Hive.box<String>(HiveBoxes.hosts);
|
||||||
if (!hostsBox.values.contains(serverUrl)) {
|
if (!hostsBox.values.contains(serverUrl)) {
|
||||||
await hostsBox.add(serverUrl);
|
await hostsBox.add(serverUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return serverUser.id;
|
return serverUser.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _getApiVersion(Dio dio) async {
|
Future<int> _getApiVersion(Dio dio) async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_getApiVersion",
|
||||||
|
"Trying to fetch API version...",
|
||||||
|
);
|
||||||
final response = await dio.get("/api/");
|
final response = await dio.get("/api/");
|
||||||
return int.parse(response.headers.value('x-api-version') ?? "3");
|
final apiVersion =
|
||||||
|
int.parse(response.headers.value('x-api-version') ?? "3");
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_getApiVersion",
|
||||||
|
"API version ($apiVersion) successfully retrieved.",
|
||||||
|
);
|
||||||
|
return apiVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches possibly updated (permissions, name, updated server version and thus new user model, ...) remote user data.
|
/// Fetches possibly updated (permissions, name, updated server version and thus new user model, ...) remote user data.
|
||||||
@@ -313,13 +455,37 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
LocalUserAccount localUserAccount,
|
LocalUserAccount localUserAccount,
|
||||||
int apiVersion,
|
int apiVersion,
|
||||||
) async {
|
) async {
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_updateRemoteUser",
|
||||||
|
"Updating paperless user object...",
|
||||||
|
);
|
||||||
final updatedPaperlessUser = await _apiFactory
|
final updatedPaperlessUser = await _apiFactory
|
||||||
.createUserApi(
|
.createUserApi(
|
||||||
sessionManager.client,
|
sessionManager.client,
|
||||||
apiVersion: apiVersion,
|
apiVersion: apiVersion,
|
||||||
)
|
)
|
||||||
.findCurrentUser();
|
.findCurrentUser();
|
||||||
|
|
||||||
localUserAccount.paperlessUser = updatedPaperlessUser;
|
localUserAccount.paperlessUser = updatedPaperlessUser;
|
||||||
await localUserAccount.save();
|
await localUserAccount.save();
|
||||||
|
_debugPrintMessage(
|
||||||
|
"_updateRemoteUser",
|
||||||
|
"Paperless user object successfully updated.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _debugPrintMessage(
|
||||||
|
String methodName,
|
||||||
|
String message, {
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
debugPrint("AuthenticationCubit#$methodName: $message");
|
||||||
|
if (error != null) {
|
||||||
|
debugPrint(error.toString());
|
||||||
|
}
|
||||||
|
if (stackTrace != null) {
|
||||||
|
debugPrintStack(stackTrace: stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ 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:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||||
@@ -147,7 +149,11 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
form[ServerAddressFormField.fkServerAddress],
|
form[ServerAddressFormField.fkServerAddress],
|
||||||
clientCert,
|
clientCert,
|
||||||
);
|
);
|
||||||
} on Exception catch (error) {
|
} on PaperlessServerException catch (error) {
|
||||||
|
showErrorMessage(context, error);
|
||||||
|
} on ServerMessageException catch (error) {
|
||||||
|
showLocalizedError(context, error.message);
|
||||||
|
} catch (error) {
|
||||||
showGenericError(context, error);
|
showGenericError(context, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
|||||||
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
||||||
try {
|
try {
|
||||||
final result = await api.findAll(newFilter);
|
final result = await api.findAll(newFilter);
|
||||||
emit(state.copyWithPaged(
|
emit(
|
||||||
hasLoaded: true,
|
state.copyWithPaged(
|
||||||
filter: newFilter,
|
hasLoaded: true,
|
||||||
value: [...state.value, result],
|
filter: newFilter,
|
||||||
));
|
value: [...state.value, result],
|
||||||
|
),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await onFilterUpdated(newFilter);
|
await onFilterUpdated(newFilter);
|
||||||
emit(state.copyWithPaged(isLoading: false));
|
emit(state.copyWithPaged(isLoading: false));
|
||||||
|
|||||||
170
lib/main.dart
170
lib/main.dart
@@ -14,6 +14,7 @@ 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';
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
import 'package:mock_server/mock_server.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/constants.dart';
|
import 'package:paperless_mobile/constants.dart';
|
||||||
@@ -22,6 +23,7 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
|||||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||||
|
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||||
import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.dart';
|
import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
||||||
@@ -41,14 +43,11 @@ import 'package:paperless_mobile/features/login/view/login_page.dart';
|
|||||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
|
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:paperless_mobile/theme.dart';
|
import 'package:paperless_mobile/theme.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
|
||||||
import 'package:mock_server/mock_server.dart';
|
|
||||||
|
|
||||||
String get defaultPreferredLocaleSubtag {
|
String get defaultPreferredLocaleSubtag {
|
||||||
String preferredLocale = Platform.localeName.split("_").first;
|
String preferredLocale = Platform.localeName.split("_").first;
|
||||||
@@ -77,95 +76,112 @@ Future<void> _initHive() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
Paint.enableDithering = true;
|
runZonedGuarded(() async {
|
||||||
if (kDebugMode) {
|
Paint.enableDithering = true;
|
||||||
// URL: http://localhost:3131
|
if (kDebugMode) {
|
||||||
// Login: admin:test
|
// URL: http://localhost:3131
|
||||||
await LocalMockApiServer(
|
// Login: admin:test
|
||||||
// RandomDelayGenerator(
|
await LocalMockApiServer(
|
||||||
// const Duration(milliseconds: 100),
|
// RandomDelayGenerator(
|
||||||
// const Duration(milliseconds: 800),
|
// const Duration(milliseconds: 100),
|
||||||
// ),
|
// const Duration(milliseconds: 800),
|
||||||
)
|
// ),
|
||||||
.start();
|
)
|
||||||
}
|
.start();
|
||||||
await _initHive();
|
}
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
await _initHive();
|
||||||
final globalSettingsBox = Hive.box<GlobalSettings>(HiveBoxes.globalSettings);
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
final globalSettings = globalSettingsBox.getValue()!;
|
final globalSettingsBox =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings);
|
||||||
|
final globalSettings = globalSettingsBox.getValue()!;
|
||||||
|
|
||||||
await findSystemLocale();
|
await findSystemLocale();
|
||||||
packageInfo = await PackageInfo.fromPlatform();
|
packageInfo = await PackageInfo.fromPlatform();
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
androidInfo = await DeviceInfoPlugin().androidInfo;
|
androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
}
|
}
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
iosInfo = await DeviceInfoPlugin().iosInfo;
|
iosInfo = await DeviceInfoPlugin().iosInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
final connectivity = Connectivity();
|
final connectivity = Connectivity();
|
||||||
final localAuthentication = LocalAuthentication();
|
final localAuthentication = LocalAuthentication();
|
||||||
final connectivityStatusService = ConnectivityStatusServiceImpl(connectivity);
|
final connectivityStatusService =
|
||||||
final localAuthService = LocalAuthenticationService(localAuthentication);
|
ConnectivityStatusServiceImpl(connectivity);
|
||||||
|
final localAuthService = LocalAuthenticationService(localAuthentication);
|
||||||
|
|
||||||
HydratedBloc.storage = await HydratedStorage.build(
|
HydratedBloc.storage = await HydratedStorage.build(
|
||||||
storageDirectory: await getApplicationDocumentsDirectory(),
|
storageDirectory: await getApplicationDocumentsDirectory(),
|
||||||
);
|
);
|
||||||
|
|
||||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||||
|
|
||||||
final languageHeaderInterceptor = LanguageHeaderInterceptor(
|
final languageHeaderInterceptor = LanguageHeaderInterceptor(
|
||||||
globalSettings.preferredLocaleSubtag,
|
globalSettings.preferredLocaleSubtag,
|
||||||
);
|
);
|
||||||
// Manages security context, required for self signed client certificates
|
// Manages security context, required for self signed client certificates
|
||||||
final sessionManager = SessionManager([
|
final sessionManager = SessionManager([
|
||||||
DioHttpErrorInterceptor(),
|
languageHeaderInterceptor,
|
||||||
languageHeaderInterceptor,
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
// Initialize Blocs/Cubits
|
// Initialize Blocs/Cubits
|
||||||
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
||||||
|
|
||||||
// Load application settings and stored authentication data
|
// Load application settings and stored authentication data
|
||||||
await connectivityCubit.initialize();
|
await connectivityCubit.initialize();
|
||||||
|
|
||||||
final localNotificationService = LocalNotificationService();
|
final localNotificationService = LocalNotificationService();
|
||||||
await localNotificationService.initialize();
|
await localNotificationService.initialize();
|
||||||
|
|
||||||
//Update language header in interceptor on language change.
|
//Update language header in interceptor on language change.
|
||||||
globalSettingsBox.listenable().addListener(() {
|
globalSettingsBox.listenable().addListener(() {
|
||||||
languageHeaderInterceptor.preferredLocaleSubtag =
|
languageHeaderInterceptor.preferredLocaleSubtag =
|
||||||
globalSettings.preferredLocaleSubtag;
|
globalSettings.preferredLocaleSubtag;
|
||||||
});
|
});
|
||||||
|
|
||||||
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
|
||||||
ChangeNotifierProvider.value(value: sessionManager),
|
|
||||||
Provider<LocalAuthenticationService>.value(value: localAuthService),
|
|
||||||
Provider<Connectivity>.value(value: connectivity),
|
|
||||||
Provider<ConnectivityStatusService>.value(
|
|
||||||
value: connectivityStatusService),
|
|
||||||
Provider<LocalNotificationService>.value(
|
|
||||||
value: localNotificationService),
|
|
||||||
Provider.value(value: DocumentChangedNotifier()),
|
|
||||||
],
|
|
||||||
child: MultiBlocProvider(
|
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
ChangeNotifierProvider.value(value: sessionManager),
|
||||||
BlocProvider(
|
Provider<LocalAuthenticationService>.value(value: localAuthService),
|
||||||
create: (context) => AuthenticationCubit(
|
Provider<Connectivity>.value(value: connectivity),
|
||||||
localAuthService, apiFactory, sessionManager),
|
Provider<ConnectivityStatusService>.value(
|
||||||
)
|
value: connectivityStatusService),
|
||||||
|
Provider<LocalNotificationService>.value(
|
||||||
|
value: localNotificationService),
|
||||||
|
Provider.value(value: DocumentChangedNotifier()),
|
||||||
],
|
],
|
||||||
child: PaperlessMobileEntrypoint(
|
child: MultiBlocProvider(
|
||||||
paperlessProviderFactory: apiFactory,
|
providers: [
|
||||||
|
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AuthenticationCubit(
|
||||||
|
localAuthService, apiFactory, sessionManager),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: PaperlessMobileEntrypoint(
|
||||||
|
paperlessProviderFactory: apiFactory,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}, (error, stack) {
|
||||||
|
String message = switch (error) {
|
||||||
|
PaperlessServerException e => e.details ?? error.toString(),
|
||||||
|
ServerMessageException e => e.message,
|
||||||
|
_ => error.toString()
|
||||||
|
};
|
||||||
|
debugPrint("An unepxected exception has occured!");
|
||||||
|
debugPrint(message);
|
||||||
|
debugPrintStack(stackTrace: stack);
|
||||||
|
// if (_rootScaffoldKey.currentContext != null) {
|
||||||
|
// ScaffoldMessenger.maybeOf(_rootScaffoldKey.currentContext!)
|
||||||
|
// ?..hideCurrentSnackBar()
|
||||||
|
// ..showSnackBar(SnackBar(content: Text(message)));
|
||||||
|
// }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||||
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user