mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-15 06:12:29 -06:00
Updated onboarding, reformatted files, improved referenced documents view, updated error handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -16,10 +17,14 @@ const authenticationKey = "authentication";
|
||||
@singleton
|
||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final LocalVault localStore;
|
||||
final GlobalErrorCubit errorCubit;
|
||||
final AuthenticationService authenticationService;
|
||||
|
||||
AuthenticationCubit(this.localStore, this.authenticationService)
|
||||
: super(AuthenticationState.initial);
|
||||
AuthenticationCubit(
|
||||
this.localStore,
|
||||
this.authenticationService,
|
||||
this.errorCubit,
|
||||
) : super(AuthenticationState.initial);
|
||||
|
||||
Future<void> initialize() {
|
||||
return restoreSessionState();
|
||||
@@ -29,68 +34,90 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
required UserCredentials credentials,
|
||||
required String serverUrl,
|
||||
ClientCertificate? clientCertificate,
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
try {
|
||||
registerSecurityContext(clientCertificate);
|
||||
} on TlsException catch (_) {
|
||||
throw const ErrorMessage(ErrorCode.invalidClientCertificateConfiguration);
|
||||
}
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: false,
|
||||
wasLoginStored: false,
|
||||
authentication: AuthenticationInformation(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
serverUrl: serverUrl,
|
||||
token: "",
|
||||
clientCertificate: clientCertificate,
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: false,
|
||||
wasLoginStored: false,
|
||||
authentication: AuthenticationInformation(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
serverUrl: serverUrl,
|
||||
token: "",
|
||||
clientCertificate: clientCertificate,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final token = await authenticationService.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
serverUrl: serverUrl,
|
||||
);
|
||||
final auth = AuthenticationInformation(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
token: token,
|
||||
serverUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
);
|
||||
final token = await authenticationService.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
serverUrl: serverUrl,
|
||||
);
|
||||
final auth = AuthenticationInformation(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
token: token,
|
||||
serverUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
|
||||
await localStore.storeAuthenticationInformation(auth);
|
||||
await localStore.storeAuthenticationInformation(auth);
|
||||
|
||||
emit(AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
wasLoginStored: false,
|
||||
authentication: auth,
|
||||
));
|
||||
emit(AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
wasLoginStored: false,
|
||||
authentication: auth,
|
||||
));
|
||||
} on TlsException catch (_) {
|
||||
const error =
|
||||
ErrorMessage(ErrorCode.invalidClientCertificateConfiguration);
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
throw error;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreSessionState() async {
|
||||
final storedAuth = await localStore.loadAuthenticationInformation();
|
||||
final appSettings =
|
||||
await localStore.loadApplicationSettings() ?? ApplicationSettingsState.defaultSettings;
|
||||
Future<void> restoreSessionState({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
final storedAuth = await localStore.loadAuthenticationInformation();
|
||||
final appSettings = await localStore.loadApplicationSettings() ??
|
||||
ApplicationSettingsState.defaultSettings;
|
||||
|
||||
if (storedAuth == null || !storedAuth.isValid) {
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
} else {
|
||||
if (!appSettings.isLocalAuthenticationEnabled ||
|
||||
await authenticationService.authenticateLocalUser("Authenticate to log back in")) {
|
||||
registerSecurityContext(storedAuth.clientCertificate);
|
||||
if (storedAuth == null || !storedAuth.isValid) {
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
wasLoginStored: true,
|
||||
authentication: storedAuth,
|
||||
),
|
||||
);
|
||||
AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
} else {
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: true));
|
||||
if (!appSettings.isLocalAuthenticationEnabled ||
|
||||
await authenticationService
|
||||
.authenticateLocalUser("Authenticate to log back in")) {
|
||||
registerSecurityContext(storedAuth.clientCertificate);
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
wasLoginStored: true,
|
||||
authentication: storedAuth,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(AuthenticationState(
|
||||
isAuthenticated: false, wasLoginStored: true));
|
||||
}
|
||||
}
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ class LocalAuthenticationCubit extends Cubit<LocalAuthenticationState> {
|
||||
LocalAuthenticationCubit() : super(LocalAuthenticationState(false));
|
||||
|
||||
Future<void> authorize(String localizedMessage) async {
|
||||
final isAuthenticationSuccessful =
|
||||
await getIt<LocalAuthentication>().authenticate(localizedReason: localizedMessage);
|
||||
final isAuthenticationSuccessful = await getIt<LocalAuthentication>()
|
||||
.authenticate(localizedReason: localizedMessage);
|
||||
if (isAuthenticationSuccessful) {
|
||||
emit(LocalAuthenticationState(true));
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:paperless_mobile/core/type/json.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
|
||||
class AuthenticationInformation {
|
||||
@@ -59,8 +59,8 @@ class AuthenticationInformation {
|
||||
password: password ?? this.password,
|
||||
token: token ?? this.token,
|
||||
serverUrl: serverUrl ?? this.serverUrl,
|
||||
clientCertificate:
|
||||
clientCertificate ?? (removeClientCertificate ? null : this.clientCertificate),
|
||||
clientCertificate: clientCertificate ??
|
||||
(removeClientCertificate ? null : this.clientCertificate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:paperless_mobile/core/type/json.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
|
||||
class ClientCertificate {
|
||||
static const bytesKey = 'bytes';
|
||||
|
||||
@@ -34,7 +34,9 @@ class AuthenticationService {
|
||||
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
||||
return data['token'];
|
||||
} else if (response.statusCode == 400 &&
|
||||
response.body.toLowerCase().contains("no required certificate was sent")) {
|
||||
response.body
|
||||
.toLowerCase()
|
||||
.contains("no required certificate was sent")) {
|
||||
throw const ErrorMessage(ErrorCode.invalidClientCertificateConfiguration);
|
||||
} else {
|
||||
throw const ErrorMessage(ErrorCode.authenticationFailed);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/di_initializer.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/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/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
@@ -73,7 +71,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Widget _buildLoginButton() {
|
||||
return ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Theme.of(context).colorScheme.primaryContainer),
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Theme.of(context).colorScheme.primaryContainer),
|
||||
elevation: const MaterialStatePropertyAll(0),
|
||||
),
|
||||
onPressed: _login,
|
||||
@@ -83,19 +82,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _login() async {
|
||||
void _login() {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
setState(() => _isLoginLoading = true);
|
||||
final form = _formKey.currentState?.value;
|
||||
getIt<AuthenticationCubit>()
|
||||
BlocProvider.of<AuthenticationCubit>(context)
|
||||
.login(
|
||||
credentials: form?[UserCredentialsFormField.fkCredentials],
|
||||
serverUrl: form?[ServerAddressFormField.fkServerAddress],
|
||||
clientCertificate: form?[ClientCertificateFormField.fkClientCertificate],
|
||||
) //TODO: Move Intro slider route push here!
|
||||
.onError<ErrorMessage>(
|
||||
(error, _) => showError(context, error),
|
||||
clientCertificate:
|
||||
form?[ClientCertificateFormField.fkClientCertificate],
|
||||
)
|
||||
.whenComplete(() => setState(() => _isLoginLoading = false));
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
const ClientCertificateFormField({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ClientCertificateFormField> createState() => _ClientCertificateFormFieldState();
|
||||
State<ClientCertificateFormField> createState() =>
|
||||
_ClientCertificateFormFieldState();
|
||||
}
|
||||
|
||||
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField> {
|
||||
class _ClientCertificateFormFieldState
|
||||
extends State<ClientCertificateFormField> {
|
||||
File? _selectedFile;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -28,14 +30,17 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
}
|
||||
assert(_selectedFile != null);
|
||||
if (_selectedFile?.path.split(".").last != 'pfx') {
|
||||
return S.of(context).loginPageClientCertificateSettingInvalidFileFormatValidationText;
|
||||
return S
|
||||
.of(context)
|
||||
.loginPageClientCertificateSettingInvalidFileFormatValidationText;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (field) {
|
||||
return ExpansionTile(
|
||||
title: Text(S.of(context).loginPageClientCertificateSettingLabel),
|
||||
subtitle: Text(S.of(context).loginPageClientCertificateSettingDescriptionText),
|
||||
subtitle: Text(
|
||||
S.of(context).loginPageClientCertificateSettingDescriptionText),
|
||||
children: [
|
||||
InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
@@ -69,7 +74,9 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
onChanged: (value) => field.didChange(
|
||||
field.value?.copyWith(passphrase: value),
|
||||
),
|
||||
label: S.of(context).loginPageClientCertificatePassphraseLabel,
|
||||
label: S
|
||||
.of(context)
|
||||
.loginPageClientCertificatePassphraseLabel,
|
||||
).padded(),
|
||||
] else
|
||||
...[]
|
||||
@@ -90,8 +97,9 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
setState(() {
|
||||
_selectedFile = file;
|
||||
});
|
||||
final changedValue = field.value?.copyWith(bytes: file.readAsBytesSync()) ??
|
||||
ClientCertificate(bytes: file.readAsBytesSync());
|
||||
final changedValue =
|
||||
field.value?.copyWith(bytes: file.readAsBytesSync()) ??
|
||||
ClientCertificate(bytes: file.readAsBytesSync());
|
||||
field.didChange(changedValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ class ObscuredInputTextFormField extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<ObscuredInputTextFormField> createState() => _ObscuredInputTextFormFieldState();
|
||||
State<ObscuredInputTextFormField> createState() =>
|
||||
_ObscuredInputTextFormFieldState();
|
||||
}
|
||||
|
||||
class _ObscuredInputTextFormFieldState extends State<ObscuredInputTextFormField> {
|
||||
class _ObscuredInputTextFormFieldState
|
||||
extends State<ObscuredInputTextFormField> {
|
||||
bool _showPassword = false;
|
||||
final FocusNode _passwordFocusNode = FocusNode();
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
}
|
||||
//https://stackoverflow.com/questions/49648022/check-whether-there-is-an-internet-connection-available-on-flutter-app
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
||||
final isReachable = await getIt<ConnectivityStatusService>().isServerReachable(address);
|
||||
final isReachable =
|
||||
await getIt<ConnectivityStatusService>().isServerReachable(address);
|
||||
if (isReachable) {
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.reachable);
|
||||
} else {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/user_credentials.model.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/password_text_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
|
||||
class UserCredentialsFormField extends StatefulWidget {
|
||||
static const fkCredentials = 'credentials';
|
||||
const UserCredentialsFormField({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UserCredentialsFormField> createState() => _UserCredentialsFormFieldState();
|
||||
State<UserCredentialsFormField> createState() =>
|
||||
_UserCredentialsFormFieldState();
|
||||
}
|
||||
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
@@ -28,7 +29,8 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
// USERNAME
|
||||
autocorrect: false,
|
||||
onChanged: (username) => field.didChange(
|
||||
field.value?.copyWith(username: username) ?? UserCredentials(username: username),
|
||||
field.value?.copyWith(username: username) ??
|
||||
UserCredentials(username: username),
|
||||
),
|
||||
validator: FormBuilderValidators.required(
|
||||
errorText: S.of(context).loginPageUsernameValidatorMessageText,
|
||||
@@ -41,7 +43,8 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
ObscuredInputTextFormField(
|
||||
label: S.of(context).loginPagePasswordFieldLabel,
|
||||
onChanged: (password) => field.didChange(
|
||||
field.value?.copyWith(password: password) ?? UserCredentials(password: password),
|
||||
field.value?.copyWith(password: password) ??
|
||||
UserCredentials(password: password),
|
||||
),
|
||||
validator: FormBuilderValidators.required(
|
||||
errorText: S.of(context).loginPagePasswordValidatorMessageText,
|
||||
|
||||
Reference in New Issue
Block a user