mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 10:07:51 -06:00
Added server address validation, success message, localization
This commit is contained in:
@@ -3,6 +3,7 @@ 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';
|
||||
|
||||
@@ -23,6 +24,7 @@ class AuthenticationAwareDioManager {
|
||||
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
||||
dio.interceptors.addAll(interceptors);
|
||||
dio.interceptors.add(RetryOnConnectionChangeInterceptor(dio: dio));
|
||||
return dio;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Select date range"),
|
||||
insetPadding: const EdgeInsets.all(24.0),
|
||||
title: Text(S.of(context).extendedDateRangeDialogTitle),
|
||||
content: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
@@ -55,7 +56,7 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
|
||||
children: [
|
||||
_buildDateRangeQueryTypeSelection(),
|
||||
Text(
|
||||
"Hint: You can either specify absolute values by selecting concrete dates, or you can specify a time range relative to the current date.",
|
||||
S.of(context).extendedDateRangeDialogHintText,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).paddedOnly(top: 8, bottom: 16),
|
||||
Builder(
|
||||
@@ -109,12 +110,12 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
|
||||
ButtonSegment(
|
||||
value: DateRangeType.absolute,
|
||||
enabled: true,
|
||||
label: Text("Absolute"),
|
||||
label: Text(S.of(context).extendedDateRangeDialogAbsoluteLabel),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: DateRangeType.relative,
|
||||
enabled: true,
|
||||
label: Text("Relative"),
|
||||
label: Text(S.of(context).extendedDateRangeDialogRelativeLabel),
|
||||
),
|
||||
],
|
||||
selected: {_selectedDateRangeType},
|
||||
|
||||
@@ -47,12 +47,14 @@ class _FormBuilderRelativeDateRangePickerState
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("Last"),
|
||||
Text(S.of(context).extendedDateRangeDialogRelativeLastLabel),
|
||||
SizedBox(
|
||||
width: 70,
|
||||
width: 80,
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Offset",
|
||||
labelText: S
|
||||
.of(context)
|
||||
.extendedDateRangeDialogRelativeAmountLabel,
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
@@ -91,7 +93,9 @@ class _FormBuilderRelativeDateRangePickerState
|
||||
onChanged: (value) =>
|
||||
field.didChange(field.value!.copyWith(unit: value)),
|
||||
decoration: InputDecoration(
|
||||
labelText: "Amount",
|
||||
labelText: S
|
||||
.of(context)
|
||||
.extendedDateRangeDialogRelativeTimeUnitLabel,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -83,8 +83,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
-4), //TODO: Wait for stable version of m3, then use AlignmentDirectional.topEnd
|
||||
isLabelVisible: appliedFiltersCount > 0,
|
||||
count: state.filter.appliedFiltersCount,
|
||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||
textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt_outlined),
|
||||
onPressed: _openDocumentFilter,
|
||||
@@ -115,7 +115,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: .9,
|
||||
maxChildSize: .9,
|
||||
snapSizes: const [.9, 1],
|
||||
builder: (context, controller) => LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
initialFilter: context.read<DocumentsCubit>().state.filter,
|
||||
|
||||
@@ -70,65 +70,74 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
S.of(context).documentFilterTitle,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
).paddedOnly(
|
||||
top: 16.0,
|
||||
left: 16.0,
|
||||
bottom: 24,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterSearchLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).paddedOnly(left: 8.0),
|
||||
_buildQueryFormField().padded(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterAdvancedLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).padded(),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: widget.initialFilter.created,
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
).padded(),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentModel.addedKey,
|
||||
initialValue: widget.initialFilter.added,
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
).padded(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
_buildTagsFormField().padded(),
|
||||
],
|
||||
).paddedOnly(bottom: 16),
|
||||
child: _buildFormList(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ListView _buildFormList(BuildContext context) {
|
||||
return ListView(
|
||||
controller: widget.scrollController,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: _buildDragHandle(context),
|
||||
),
|
||||
Text(
|
||||
S.of(context).documentFilterTitle,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
).paddedOnly(
|
||||
top: 16.0,
|
||||
left: 16.0,
|
||||
bottom: 24,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterSearchLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).paddedOnly(left: 8.0),
|
||||
_buildQueryFormField().padded(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterAdvancedLabel,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).padded(),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: widget.initialFilter.created,
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
).padded(),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
name: DocumentModel.addedKey,
|
||||
initialValue: widget.initialFilter.added,
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
).padded(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
_buildTagsFormField().padded(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Container _buildDragHandle(BuildContext context) {
|
||||
return Container(
|
||||
// According to m3 spec
|
||||
width: 32,
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<Tag>, LabelState<Tag>> _buildTagsFormField() {
|
||||
return BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
|
||||
76
lib/features/home/view/widget/verify_identity_page.dart
Normal file
76
lib/features/home/view/widget/verify_identity_page.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class VerifyIdentityPage extends StatelessWidget {
|
||||
const VerifyIdentityPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
title: Text(S.of(context).verifyIdentityPageTitle),
|
||||
),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).verifyIdentityPageDescriptionText)
|
||||
.paddedSymmetrically(horizontal: 16),
|
||||
const Icon(
|
||||
Icons.fingerprint,
|
||||
size: 96,
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
runAlignment: WrapAlignment.spaceBetween,
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _logout(context),
|
||||
child: Text(
|
||||
S.of(context).verifyIdentityPageLogoutButtonLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => context
|
||||
.read<AuthenticationCubit>()
|
||||
.restoreSessionState(context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.state
|
||||
.isLocalAuthenticationEnabled),
|
||||
child: Text(S
|
||||
.of(context)
|
||||
.verifyIdentityPageVerifyIdentityButtonLabel),
|
||||
),
|
||||
],
|
||||
).padded(16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _logout(BuildContext context) {
|
||||
context.read<AuthenticationCubit>().logout();
|
||||
context.read<LabelRepository<Tag>>().clear();
|
||||
context.read<LabelRepository<Correspondent>>().clear();
|
||||
context.read<LabelRepository<DocumentType>>().clear();
|
||||
context.read<LabelRepository<StoragePath>>().clear();
|
||||
context.read<SavedViewRepository>().clear();
|
||||
HydratedBloc.storage.clear();
|
||||
}
|
||||
}
|
||||
@@ -17,26 +17,49 @@ class ServerAddressFormField extends StatefulWidget {
|
||||
}
|
||||
|
||||
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();
|
||||
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.undefined;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormBuilderTextField(
|
||||
key: const ValueKey('login-server-address'),
|
||||
controller: _textEditingController,
|
||||
name: ServerAddressFormField.fkServerAddress,
|
||||
validator: FormBuilderValidators.required(
|
||||
errorText: S.of(context).loginPageServerUrlValidatorMessageText,
|
||||
validator: FormBuilderValidators.compose(
|
||||
[
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
S.of(context).loginPageServerUrlValidatorMessageRequiredText,
|
||||
),
|
||||
FormBuilderValidators.match(
|
||||
_urlRegex.pattern,
|
||||
errorText: S
|
||||
.of(context)
|
||||
.loginPageServerUrlValidatorMessageInvalidAddressText,
|
||||
),
|
||||
],
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(r".*/$"),
|
||||
FilteringTextInputFormatter.deny(r"\s"),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: _buildIsReachableIcon(),
|
||||
hintText: "http://192.168.1.50:8000",
|
||||
labelText: S.of(context).loginPageServerUrlFieldLabel,
|
||||
),
|
||||
onChanged: _updateIsAddressReachableStatus,
|
||||
onSubmitted: (value) {
|
||||
if (value == null) return;
|
||||
// Remove trailing slash if it is a valid address.
|
||||
final address = value.trim();
|
||||
_textEditingController.text = address;
|
||||
if (_urlRegex.hasMatch(address) && address.endsWith("/")) {
|
||||
_textEditingController.text = address.replaceAll(RegExp(r'\/$'), '');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,7 +83,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
}
|
||||
|
||||
void _updateIsAddressReachableStatus(String? address) async {
|
||||
if (address == null || address.isEmpty) {
|
||||
if (address == null || !_urlRegex.hasMatch(address)) {
|
||||
setState(() {
|
||||
_reachabilityStatus = ReachabilityStatus.undefined;
|
||||
});
|
||||
@@ -70,12 +93,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.testing);
|
||||
final isReachable = await context
|
||||
.read<ConnectivityStatusService>()
|
||||
.isServerReachable(address);
|
||||
if (isReachable) {
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.reachable);
|
||||
} else {
|
||||
setState(() => _reachabilityStatus = ReachabilityStatus.notReachable);
|
||||
}
|
||||
.isServerReachable(address.trim());
|
||||
setState(
|
||||
() => _reachabilityStatus = isReachable
|
||||
? ReachabilityStatus.reachable
|
||||
: ReachabilityStatus.notReachable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,11 +260,25 @@
|
||||
"@errorMessageUnsupportedFileFormat": {},
|
||||
"errorReportLabel": "NAHLÁSIT",
|
||||
"@errorReportLabel": {},
|
||||
"extendedDateRangePickerAfterLabel": "",
|
||||
"extendedDateRangeDialogAbsoluteLabel": "Absolute",
|
||||
"@extendedDateRangeDialogAbsoluteLabel": {},
|
||||
"extendedDateRangeDialogHintText": "Hint: Apart from concrete dates, you can also specify a time range relative to the current date.",
|
||||
"@extendedDateRangeDialogHintText": {},
|
||||
"extendedDateRangeDialogRelativeAmountLabel": "Amount",
|
||||
"@extendedDateRangeDialogRelativeAmountLabel": {},
|
||||
"extendedDateRangeDialogRelativeLabel": "Relative",
|
||||
"@extendedDateRangeDialogRelativeLabel": {},
|
||||
"extendedDateRangeDialogRelativeLastLabel": "Last",
|
||||
"@extendedDateRangeDialogRelativeLastLabel": {},
|
||||
"extendedDateRangeDialogRelativeTimeUnitLabel": "Time unit",
|
||||
"@extendedDateRangeDialogRelativeTimeUnitLabel": {},
|
||||
"extendedDateRangeDialogTitle": "Select date range",
|
||||
"@extendedDateRangeDialogTitle": {},
|
||||
"extendedDateRangePickerAfterLabel": "After",
|
||||
"@extendedDateRangePickerAfterLabel": {},
|
||||
"extendedDateRangePickerBeforeLabel": "",
|
||||
"extendedDateRangePickerBeforeLabel": "Before",
|
||||
"@extendedDateRangePickerBeforeLabel": {},
|
||||
"extendedDateRangePickerDayText": "{count, plural, other{}}",
|
||||
"extendedDateRangePickerDayText": "{count, plural, zero{} one{day} other{days}}",
|
||||
"@extendedDateRangePickerDayText": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
@@ -284,9 +298,9 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerLastText": "",
|
||||
"extendedDateRangePickerLastText": "Last",
|
||||
"@extendedDateRangePickerLastText": {},
|
||||
"extendedDateRangePickerLastWeeksLabel": "{count, plural, other{}}",
|
||||
"extendedDateRangePickerLastWeeksLabel": "{count, plural, zero{} one{Last week} other{Last {count} weeks}}",
|
||||
"@extendedDateRangePickerLastWeeksLabel": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
@@ -298,7 +312,7 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerMonthText": "{count, plural, other{}}",
|
||||
"extendedDateRangePickerMonthText": "{count, plural, zero{} one{month} other{months}}",
|
||||
"@extendedDateRangePickerMonthText": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
@@ -306,13 +320,13 @@
|
||||
},
|
||||
"extendedDateRangePickerToLabel": "Do",
|
||||
"@extendedDateRangePickerToLabel": {},
|
||||
"extendedDateRangePickerWeekText": "{count, plural, other{}}",
|
||||
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{week} other{weeks}}",
|
||||
"@extendedDateRangePickerWeekText": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerYearText": "{count, plural, other{}}",
|
||||
"extendedDateRangePickerYearText": "{count, plural, zero{} one{year} other{years}}",
|
||||
"@extendedDateRangePickerYearText": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
@@ -424,8 +438,10 @@
|
||||
"@loginPagePasswordValidatorMessageText": {},
|
||||
"loginPageServerUrlFieldLabel": "'Adresa serveru",
|
||||
"@loginPageServerUrlFieldLabel": {},
|
||||
"loginPageServerUrlValidatorMessageText": "Adresa serveru nesmí být prázdná.",
|
||||
"@loginPageServerUrlValidatorMessageText": {},
|
||||
"loginPageServerUrlValidatorMessageInvalidAddressText": "Invalid address.",
|
||||
"@loginPageServerUrlValidatorMessageInvalidAddressText": {},
|
||||
"loginPageServerUrlValidatorMessageRequiredText": "Adresa serveru nesmí být prázdná.",
|
||||
"@loginPageServerUrlValidatorMessageRequiredText": {},
|
||||
"loginPageTitle": "Propojit s Paperless",
|
||||
"@loginPageTitle": {},
|
||||
"loginPageUsernameLabel": "Jméno uživatele",
|
||||
@@ -499,5 +515,13 @@
|
||||
"tagInboxTagPropertyLabel": "Tag inboxu",
|
||||
"@tagInboxTagPropertyLabel": {},
|
||||
"uploadPageAutomaticallInferredFieldsHintText": "Pokud specifikuješ hodnoty pro tato pole, paperless instance nebude automaticky přiřazovat naučené hodnoty. Pokud mají být tato pole automaticky vyplňována, nevyplňujte zde nic.",
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {}
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {},
|
||||
"verifyIdentityPageDescriptionText": "Use the configured biometric factor to authenticate and unlock your documents.",
|
||||
"@verifyIdentityPageDescriptionText": {},
|
||||
"verifyIdentityPageLogoutButtonLabel": "Disconnect",
|
||||
"@verifyIdentityPageLogoutButtonLabel": {},
|
||||
"verifyIdentityPageTitle": "Verify your identity",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
}
|
||||
@@ -260,6 +260,20 @@
|
||||
"@errorMessageUnsupportedFileFormat": {},
|
||||
"errorReportLabel": "MELDEN",
|
||||
"@errorReportLabel": {},
|
||||
"extendedDateRangeDialogAbsoluteLabel": "Absolut",
|
||||
"@extendedDateRangeDialogAbsoluteLabel": {},
|
||||
"extendedDateRangeDialogHintText": "Hinweis: Neben konkreten Daten kannst du den Zeitraum auch über eine relative Zeitspanne einschränken.",
|
||||
"@extendedDateRangeDialogHintText": {},
|
||||
"extendedDateRangeDialogRelativeAmountLabel": "Anzahl",
|
||||
"@extendedDateRangeDialogRelativeAmountLabel": {},
|
||||
"extendedDateRangeDialogRelativeLabel": "Relativ",
|
||||
"@extendedDateRangeDialogRelativeLabel": {},
|
||||
"extendedDateRangeDialogRelativeLastLabel": "Letzte",
|
||||
"@extendedDateRangeDialogRelativeLastLabel": {},
|
||||
"extendedDateRangeDialogRelativeTimeUnitLabel": "Zeiteinheit",
|
||||
"@extendedDateRangeDialogRelativeTimeUnitLabel": {},
|
||||
"extendedDateRangeDialogTitle": "Wähle Zeitraum",
|
||||
"@extendedDateRangeDialogTitle": {},
|
||||
"extendedDateRangePickerAfterLabel": "Nach",
|
||||
"@extendedDateRangePickerAfterLabel": {},
|
||||
"extendedDateRangePickerBeforeLabel": "Vor",
|
||||
@@ -346,7 +360,7 @@
|
||||
"@inboxPageMarkAllAsSeenConfirmationDialogTitleText": {},
|
||||
"inboxPageMarkAllAsSeenLabel": "Alle gesehen",
|
||||
"@inboxPageMarkAllAsSeenLabel": {},
|
||||
"inboxPageMarkAsSeenText": "Als gelesen markieren",
|
||||
"inboxPageMarkAsSeenText": "Als gesehen markieren",
|
||||
"@inboxPageMarkAsSeenText": {},
|
||||
"inboxPageNoNewDocumentsRefreshLabel": "Neu laden",
|
||||
"@inboxPageNoNewDocumentsRefreshLabel": {},
|
||||
@@ -424,8 +438,10 @@
|
||||
"@loginPagePasswordValidatorMessageText": {},
|
||||
"loginPageServerUrlFieldLabel": "Server-Adresse",
|
||||
"@loginPageServerUrlFieldLabel": {},
|
||||
"loginPageServerUrlValidatorMessageText": "Server-Addresse darf nicht leer sein.",
|
||||
"@loginPageServerUrlValidatorMessageText": {},
|
||||
"loginPageServerUrlValidatorMessageInvalidAddressText": "Ungültige Adresse.",
|
||||
"@loginPageServerUrlValidatorMessageInvalidAddressText": {},
|
||||
"loginPageServerUrlValidatorMessageRequiredText": "Server-Addresse darf nicht leer sein.",
|
||||
"@loginPageServerUrlValidatorMessageRequiredText": {},
|
||||
"loginPageTitle": "Mit Paperless verbinden",
|
||||
"@loginPageTitle": {},
|
||||
"loginPageUsernameLabel": "Nutzername",
|
||||
@@ -499,5 +515,13 @@
|
||||
"tagInboxTagPropertyLabel": "Posteingangs-Tag",
|
||||
"@tagInboxTagPropertyLabel": {},
|
||||
"uploadPageAutomaticallInferredFieldsHintText": "Wenn Werte für diese Felder angegeben werden, wird Paperless nicht automatisch einen Wert zuweisen. Wenn diese Felder automatisch von Paperless erkannt werden sollen, sollten die Felder leer bleiben.",
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {}
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {},
|
||||
"verifyIdentityPageDescriptionText": "Benutze den konfigurierten Biometrischen Faktor um dich zu identifizieren und auf deine Dokumente zuzugreifen.",
|
||||
"@verifyIdentityPageDescriptionText": {},
|
||||
"verifyIdentityPageLogoutButtonLabel": "Verbindung trennen",
|
||||
"@verifyIdentityPageLogoutButtonLabel": {},
|
||||
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
}
|
||||
@@ -260,6 +260,20 @@
|
||||
"@errorMessageUnsupportedFileFormat": {},
|
||||
"errorReportLabel": "REPORT",
|
||||
"@errorReportLabel": {},
|
||||
"extendedDateRangeDialogAbsoluteLabel": "Absolute",
|
||||
"@extendedDateRangeDialogAbsoluteLabel": {},
|
||||
"extendedDateRangeDialogHintText": "Hint: Apart from concrete dates, you can also specify a time range relative to the current date.",
|
||||
"@extendedDateRangeDialogHintText": {},
|
||||
"extendedDateRangeDialogRelativeAmountLabel": "Amount",
|
||||
"@extendedDateRangeDialogRelativeAmountLabel": {},
|
||||
"extendedDateRangeDialogRelativeLabel": "Relative",
|
||||
"@extendedDateRangeDialogRelativeLabel": {},
|
||||
"extendedDateRangeDialogRelativeLastLabel": "Last",
|
||||
"@extendedDateRangeDialogRelativeLastLabel": {},
|
||||
"extendedDateRangeDialogRelativeTimeUnitLabel": "Time unit",
|
||||
"@extendedDateRangeDialogRelativeTimeUnitLabel": {},
|
||||
"extendedDateRangeDialogTitle": "Select date range",
|
||||
"@extendedDateRangeDialogTitle": {},
|
||||
"extendedDateRangePickerAfterLabel": "After",
|
||||
"@extendedDateRangePickerAfterLabel": {},
|
||||
"extendedDateRangePickerBeforeLabel": "Before",
|
||||
@@ -424,8 +438,10 @@
|
||||
"@loginPagePasswordValidatorMessageText": {},
|
||||
"loginPageServerUrlFieldLabel": "Server Address",
|
||||
"@loginPageServerUrlFieldLabel": {},
|
||||
"loginPageServerUrlValidatorMessageText": "Server address must not be empty.",
|
||||
"@loginPageServerUrlValidatorMessageText": {},
|
||||
"loginPageServerUrlValidatorMessageInvalidAddressText": "Invalid address.",
|
||||
"@loginPageServerUrlValidatorMessageInvalidAddressText": {},
|
||||
"loginPageServerUrlValidatorMessageRequiredText": "Server address must not be empty.",
|
||||
"@loginPageServerUrlValidatorMessageRequiredText": {},
|
||||
"loginPageTitle": "Connect to Paperless",
|
||||
"@loginPageTitle": {},
|
||||
"loginPageUsernameLabel": "Username",
|
||||
@@ -499,5 +515,13 @@
|
||||
"tagInboxTagPropertyLabel": "Inbox-Tag",
|
||||
"@tagInboxTagPropertyLabel": {},
|
||||
"uploadPageAutomaticallInferredFieldsHintText": "If you specify values for these fields, your paperless instance will not automatically derive a value. If you want these values to be automatically populated by your server, leave the fields blank.",
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {}
|
||||
"@uploadPageAutomaticallInferredFieldsHintText": {},
|
||||
"verifyIdentityPageDescriptionText": "Use the configured biometric factor to authenticate and unlock your documents.",
|
||||
"@verifyIdentityPageDescriptionText": {},
|
||||
"verifyIdentityPageLogoutButtonLabel": "Disconnect",
|
||||
"@verifyIdentityPageLogoutButtonLabel": {},
|
||||
"verifyIdentityPageTitle": "Verify your identity",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.
|
||||
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/home/view/home_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
@@ -349,6 +350,9 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
),
|
||||
);
|
||||
if (success) {
|
||||
Fluttertoast.showToast(
|
||||
msg: S.of(context).documentUploadSuccessText,
|
||||
);
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
@@ -397,7 +401,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
} else {
|
||||
if (authentication.wasLoginStored &&
|
||||
!(authentication.wasLocalAuthenticationSuccessful ?? false)) {
|
||||
return const BiometricAuthenticationPage();
|
||||
return const VerifyIdentityPage();
|
||||
}
|
||||
return const LoginPage();
|
||||
}
|
||||
@@ -406,50 +410,3 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BiometricAuthenticationPage extends StatelessWidget {
|
||||
const BiometricAuthenticationPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"The app is locked!",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
"You can now either try to authenticate again or disconnect from the current server.",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).padded(),
|
||||
const SizedBox(height: 48),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<AuthenticationCubit>().logout();
|
||||
context.read();
|
||||
HydratedBloc.storage.clear();
|
||||
},
|
||||
child: const Text("Log out"),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => context
|
||||
.read<AuthenticationCubit>()
|
||||
.restoreSessionState(context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.state
|
||||
.isLocalAuthenticationEnabled),
|
||||
child: const Text("Authenticate"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,24 @@ void showSnackBar(
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
message + (details != null ? ' ($details)' : ''),
|
||||
content: RichText(
|
||||
maxLines: 5,
|
||||
text: TextSpan(
|
||||
text: message,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
if (details != null)
|
||||
TextSpan(
|
||||
text: "\n$details",
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
action: action != null
|
||||
? SnackBarAction(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:paperless_api/src/models/paperless_server_exception.dart';
|
||||
import 'package:paperless_api/src/modules/authentication_api/authentication_api.dart';
|
||||
@@ -12,7 +14,6 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||
required String username,
|
||||
required String password,
|
||||
}) async {
|
||||
print(client.hashCode);
|
||||
late Response response;
|
||||
try {
|
||||
response = await client.post(
|
||||
@@ -29,7 +30,11 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
||||
httpStatusCode: error.response?.statusCode,
|
||||
);
|
||||
} else {
|
||||
throw error.error;
|
||||
log(error.message);
|
||||
throw PaperlessServerException(
|
||||
ErrorCode.authenticationFailed,
|
||||
details: error.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
data: path.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.created) {
|
||||
return StoragePath.fromJson(jsonDecode(response.data));
|
||||
return StoragePath.fromJson(response.data);
|
||||
}
|
||||
throw PaperlessServerException(ErrorCode.storagePathCreateFailed,
|
||||
httpStatusCode: response.statusCode);
|
||||
@@ -282,7 +282,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
data: path.toJson(),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return StoragePath.fromJson(jsonDecode(response.data));
|
||||
return StoragePath.fromJson(response.data);
|
||||
}
|
||||
throw const PaperlessServerException(ErrorCode.unknown);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ dependencies:
|
||||
json_annotation: ^4.7.0
|
||||
pretty_dio_logger: ^1.2.0-beta-1
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user