mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 09:15:48 -06:00
Migrated to flutter master channel, some adaptations to new m3 specs
This commit is contained in:
@@ -124,7 +124,13 @@ flutter install
|
||||
```
|
||||
|
||||
## Languages and Translations
|
||||
If you want to contribute to translate the app into your language, create an issue and you will be invited to the Localizely project.
|
||||
If you want to contribute to translate the app into your language, create a new <a href="https://github.com/astubenbord/paperless-mobile/discussions/categories/languages-and-translations">Discussion</a> and you will be invited to the <a href="https://localizely.com/">Localizely</a> project.
|
||||
|
||||
Thanks to the following contributors for providing translations:
|
||||
- German and English by <a href="https://github.com/astubenbord">astubenbord</a>
|
||||
- Czech language by <a href="https://github.com/svetlemodry">svetlemodry</a>
|
||||
|
||||
This project is registered as an open source project in Localizely, which offers full benefits for free!
|
||||
|
||||
<!-- ROADMAP -->
|
||||
## Roadmap
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/di_test_mocks.mocks.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/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
@@ -49,7 +48,6 @@ void main() async {
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
// Mocked classes
|
||||
@@ -97,7 +95,6 @@ void main() async {
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
// Mocked classes
|
||||
|
||||
@@ -149,7 +146,6 @@ void main() async {
|
||||
));
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
@@ -199,7 +195,6 @@ void main() async {
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/main.dart';
|
||||
|
||||
Future<TestingFrameworkVariables> initializeTestingFramework(
|
||||
{String languageCode = 'en'}) async {
|
||||
@@ -35,5 +34,5 @@ Future<void> initAndLaunchTestApp(
|
||||
Future<void> Function() initializationCallback,
|
||||
) async {
|
||||
await initializationCallback();
|
||||
runApp(const PaperlessMobileEntrypoint());
|
||||
//runApp(const PaperlessMobileEntrypoint(authenticationCubit: ),));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/models/saved_view_model.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
@@ -8,20 +7,21 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
|
||||
SavedViewRepositoryImpl(this._api);
|
||||
|
||||
final BehaviorSubject<Map<int, SavedView>> _subject =
|
||||
BehaviorSubject.seeded({});
|
||||
final BehaviorSubject<Map<int, SavedView>?> _subject = BehaviorSubject();
|
||||
|
||||
@override
|
||||
Stream<Map<int, SavedView>> get savedViews =>
|
||||
Stream<Map<int, SavedView>?> get savedViews =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
void clear() {}
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SavedView> create(SavedView view) async {
|
||||
final created = await _api.save(view);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._subject.valueOrNull ?? {}}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
@@ -30,7 +30,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
@override
|
||||
Future<int> delete(SavedView view) async {
|
||||
await _api.delete(view);
|
||||
final updatedState = {..._subject.value}..remove(view.id);
|
||||
final updatedState = {..._subject.valueOrNull ?? {}}..remove(view.id);
|
||||
_subject.add(updatedState);
|
||||
return view.id!;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
@override
|
||||
Future<SavedView?> find(int id) async {
|
||||
final found = await _api.find(id);
|
||||
final updatedState = {..._subject.value}
|
||||
final updatedState = {..._subject.valueOrNull ?? {}}
|
||||
..update(id, (_) => found, ifAbsent: () => found);
|
||||
_subject.add(updatedState);
|
||||
return found;
|
||||
@@ -48,7 +48,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||
final found = await _api.findAll(ids);
|
||||
final updatedState = {
|
||||
..._subject.value,
|
||||
..._subject.valueOrNull ?? {},
|
||||
...{for (final view in found) view.id!: view},
|
||||
};
|
||||
_subject.add(updatedState);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/src/widgets/container.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
abstract class SavedViewRepository {
|
||||
Stream<Map<int, SavedView>> get savedViews;
|
||||
Stream<Map<int, SavedView>?> get savedViews;
|
||||
|
||||
Future<SavedView> create(SavedView view);
|
||||
Future<SavedView?> find(int id);
|
||||
|
||||
@@ -45,7 +45,7 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
|
||||
).padded(),
|
||||
Text(
|
||||
'Description',
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padded(),
|
||||
FormBuilderTextField(
|
||||
name: shortDescriptionKey,
|
||||
@@ -70,7 +70,7 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
|
||||
children: [
|
||||
Text(
|
||||
'Stack Trace',
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).paddedOnly(top: 8.0, left: 8.0, right: 8.0),
|
||||
TextButton.icon(
|
||||
label: const Text('Copy'),
|
||||
@@ -81,11 +81,11 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
|
||||
),
|
||||
Text(
|
||||
'Since stack traces cannot be attached to the GitHub issue url, please copy the content of the stackTrace and paste it in the issue description. This will greatly increase the chance of quickly resolving the issue!',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).padded(),
|
||||
Text(
|
||||
widget.stackTrace.toString(),
|
||||
style: Theme.of(context).textTheme.overline,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).padded(),
|
||||
]
|
||||
],
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
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:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_relative_date_range_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class ExtendedDateRangeDialog extends StatefulWidget {
|
||||
final DateRangeQuery initialValue;
|
||||
@@ -56,13 +51,13 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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.",
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
_buildDateRangeQueryTypeSelection(),
|
||||
const SizedBox(height: 16),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).paddedOnly(top: 8, bottom: 16),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
switch (_selectedDateRangeType) {
|
||||
@@ -106,21 +101,23 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
|
||||
}
|
||||
|
||||
Widget _buildDateRangeQueryTypeSelection() {
|
||||
return Row(
|
||||
children: [
|
||||
ChoiceChip(
|
||||
label: Text('Absolute'),
|
||||
selected: _selectedDateRangeType == DateRangeType.absolute,
|
||||
onSelected: (value) =>
|
||||
setState(() => _selectedDateRangeType = DateRangeType.absolute),
|
||||
).paddedOnly(right: 8.0),
|
||||
ChoiceChip(
|
||||
label: Text('Relative'),
|
||||
selected: _selectedDateRangeType == DateRangeType.relative,
|
||||
onSelected: (value) =>
|
||||
setState(() => _selectedDateRangeType = DateRangeType.relative),
|
||||
return SegmentedButton<DateRangeType>(
|
||||
multiSelectionEnabled: false,
|
||||
onSelectionChanged: (selection) =>
|
||||
setState(() => _selectedDateRangeType = selection.first),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: DateRangeType.absolute,
|
||||
enabled: true,
|
||||
label: Text("Absolute"),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: DateRangeType.relative,
|
||||
enabled: true,
|
||||
label: Text("Relative"),
|
||||
),
|
||||
],
|
||||
selected: {_selectedDateRangeType},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,15 +97,15 @@ class _FormBuilderRelativeDateRangePickerState
|
||||
),
|
||||
],
|
||||
),
|
||||
RelativeDateRangePickerHelper(
|
||||
field: field,
|
||||
onChanged: (value) {
|
||||
if (value is RelativeDateRangeQuery) {
|
||||
setState(() => _offset = value.offset);
|
||||
_offsetTextEditingController.text = _offset.toString();
|
||||
}
|
||||
},
|
||||
),
|
||||
// RelativeDateRangePickerHelper(
|
||||
// field: field,
|
||||
// onChanged: (value) {
|
||||
// if (value is RelativeDateRangeQuery) {
|
||||
// setState(() => _offset = value.offset);
|
||||
// _offsetTextEditingController.text = _offset.toString();
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -121,7 +121,7 @@ class HighlightedText extends StatelessWidget {
|
||||
TextSpan _normalSpan(String value, BuildContext context) {
|
||||
return TextSpan(
|
||||
text: value,
|
||||
style: style ?? Theme.of(context).textTheme.bodyText2,
|
||||
style: style ?? Theme.of(context).textTheme.bodyMedium,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:paperless_mobile/di_initializer.config.dart';
|
||||
import 'package:paperless_mobile/di_modules.dart';
|
||||
import 'package:paperless_mobile/di_paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@@ -21,7 +20,7 @@ void configureDependencies(String environment) =>
|
||||
///
|
||||
/// Registers new security context, which will be used by the HttpClient, see [RegisterModule].
|
||||
///
|
||||
void registerSecurityContext(ClientCertificate? cert) {
|
||||
Future<void> registerSecurityContext(ClientCertificate? cert) async {
|
||||
var context = SecurityContext();
|
||||
if (cert != null) {
|
||||
context = context
|
||||
@@ -29,6 +28,6 @@ void registerSecurityContext(ClientCertificate? cert) {
|
||||
..useCertificateChainBytes(cert.bytes, password: cert.passphrase)
|
||||
..setTrustedCertificatesBytes(cert.bytes, password: cert.passphrase);
|
||||
}
|
||||
getIt.unregister<SecurityContext>();
|
||||
await getIt.unregister<SecurityContext>();
|
||||
getIt.registerSingleton<SecurityContext>(context);
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ class DocumentDetailsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
bool _isDownloadPending = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
@@ -107,11 +105,11 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
color: Colors
|
||||
.black, //TODO: check if there is a way to dynamically determine color...
|
||||
),
|
||||
onPressed: () => Navigator.pop(
|
||||
context,
|
||||
onPressed: () => Navigator.of(context).pop(
|
||||
BlocProvider.of<DocumentDetailsCubit>(context)
|
||||
.state
|
||||
.document),
|
||||
.document,
|
||||
),
|
||||
),
|
||||
floating: true,
|
||||
pinned: true,
|
||||
@@ -238,12 +236,12 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
children: [
|
||||
_DetailsItem.text(DateFormat().format(document.modified),
|
||||
label: S.of(context).documentModifiedPropertyLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
context: context)
|
||||
.paddedOnly(bottom: 16),
|
||||
_DetailsItem.text(DateFormat().format(document.added),
|
||||
label: S.of(context).documentAddedPropertyLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
context: context)
|
||||
.paddedSymmetrically(vertical: 16),
|
||||
_DetailsItem(
|
||||
label: S.of(context).documentArchiveSerialNumberPropertyLongLabel,
|
||||
content: document.archiveSerialNumber != null
|
||||
@@ -255,30 +253,26 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
onPressed:
|
||||
widget.allowEdit ? () => _assignAsn(document) : null,
|
||||
),
|
||||
),
|
||||
_separator(),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
_DetailsItem.text(
|
||||
meta.mediaFilename,
|
||||
context: context,
|
||||
label: S.of(context).documentMetaDataMediaFilenamePropertyLabel,
|
||||
),
|
||||
_separator(),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
_DetailsItem.text(
|
||||
meta.originalChecksum,
|
||||
context: context,
|
||||
label: S.of(context).documentMetaDataChecksumLabel,
|
||||
),
|
||||
_separator(),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
_DetailsItem.text(formatBytes(meta.originalSize, 2),
|
||||
label: S.of(context).documentMetaDataOriginalFileSizeLabel,
|
||||
context: context),
|
||||
_separator(),
|
||||
context: context)
|
||||
.paddedSymmetrically(vertical: 16),
|
||||
_DetailsItem.text(
|
||||
meta.originalMimeType,
|
||||
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
|
||||
context: context,
|
||||
),
|
||||
_separator(),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -295,16 +289,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
Widget _buildDocumentContentView(DocumentModel document, String? match) {
|
||||
return SingleChildScrollView(
|
||||
child: _DetailsItem(
|
||||
content: HighlightedText(
|
||||
child: HighlightedText(
|
||||
text: document.content ?? "",
|
||||
highlights: match == null ? [] : match.split(" "),
|
||||
style: Theme.of(context).textTheme.bodyText2,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
caseSensitive: false,
|
||||
),
|
||||
label: S.of(context).documentDetailsPageTabContentLabel,
|
||||
),
|
||||
);
|
||||
).paddedOnly(top: 8);
|
||||
}
|
||||
|
||||
Widget _buildDocumentOverview(DocumentModel document, String? match) {
|
||||
@@ -314,50 +305,50 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
content: HighlightedText(
|
||||
text: document.title,
|
||||
highlights: match?.split(" ") ?? <String>[],
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
label: S.of(context).documentTitlePropertyLabel,
|
||||
),
|
||||
_separator(),
|
||||
).paddedOnly(bottom: 16),
|
||||
_DetailsItem.text(
|
||||
DateFormat.yMMMd().format(document.created),
|
||||
context: context,
|
||||
label: S.of(context).documentCreatedPropertyLabel,
|
||||
),
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
Visibility(
|
||||
visible: document.documentType != null,
|
||||
child: _DetailsItem(
|
||||
content: DocumentTypeWidget(
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge,
|
||||
isClickable: widget.isLabelClickable,
|
||||
documentTypeId: document.documentType,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
),
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
Visibility(
|
||||
visible: document.correspondent != null,
|
||||
child: _DetailsItem(
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
content: CorrespondentWidget(
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge,
|
||||
isClickable: widget.isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
),
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
Visibility(
|
||||
visible: document.storagePath != null,
|
||||
child: _DetailsItem(
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
content: StoragePathWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
pathId: document.storagePath,
|
||||
afterSelected: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
),
|
||||
_separator(),
|
||||
_DetailsItem(
|
||||
Visibility(
|
||||
visible: document.tags.isNotEmpty,
|
||||
child: _DetailsItem(
|
||||
label: S.of(context).documentTagsPropertyLabel,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
@@ -368,6 +359,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
onTagSelected: (int tagId) {},
|
||||
),
|
||||
),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
),
|
||||
// _separator(),
|
||||
// FutureBuilder<List<SimilarDocumentModel>>(
|
||||
@@ -396,10 +388,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _separator() {
|
||||
return const SizedBox(height: 32.0);
|
||||
}
|
||||
|
||||
///
|
||||
/// Downloads file to temporary directory, from which it can then be shared.
|
||||
///
|
||||
@@ -477,10 +465,7 @@ class _DetailsItem extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline5
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
content,
|
||||
],
|
||||
@@ -492,7 +477,7 @@ class _DetailsItem extends StatelessWidget {
|
||||
String text, {
|
||||
required this.label,
|
||||
required BuildContext context,
|
||||
}) : content = Text(text, style: Theme.of(context).textTheme.bodyText2);
|
||||
}) : content = Text(text, style: Theme.of(context).textTheme.bodyLarge);
|
||||
}
|
||||
|
||||
class ColoredTabBar extends Container implements PreferredSizeWidget {
|
||||
|
||||
@@ -208,7 +208,7 @@ class _DocumentUploadPreparationPageState
|
||||
S
|
||||
.of(context)
|
||||
.uploadPageAutomaticallInferredFieldsHintText,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
].padded(),
|
||||
),
|
||||
|
||||
@@ -88,6 +88,14 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
emit(DocumentsState(filter: filter, value: [result], isLoaded: true));
|
||||
}
|
||||
|
||||
Future<void> resetFilter() {
|
||||
final filter = DocumentFilter.initial.copyWith(
|
||||
sortField: state.filter.sortField,
|
||||
sortOrder: state.filter.sortOrder,
|
||||
);
|
||||
return updateFilter(filter: filter);
|
||||
}
|
||||
|
||||
///
|
||||
/// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
|
||||
///
|
||||
|
||||
@@ -88,7 +88,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
isLabelVisible: appliedFiltersCount > 0,
|
||||
count: state.filter.appliedFiltersCount,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt_rounded),
|
||||
child: const Icon(Icons.filter_alt_outlined),
|
||||
onPressed: _openDocumentFilter,
|
||||
),
|
||||
);
|
||||
@@ -146,22 +146,28 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
switch (settings.preferredViewType) {
|
||||
case ViewType.list:
|
||||
child = DocumentListView(
|
||||
onTap: _openDetails,
|
||||
state: state,
|
||||
onTap: _openDetails,
|
||||
onSelected: _onSelected,
|
||||
pagingController: _pagingController,
|
||||
hasInternetConnection: isConnected,
|
||||
onTagSelected: _addTagToFilter,
|
||||
onCorrespondentSelected: _addCorrespondentToFilter,
|
||||
onDocumentTypeSelected: _addDocumentTypeToFilter,
|
||||
onStoragePathSelected: _addStoragePathToFilter,
|
||||
);
|
||||
break;
|
||||
case ViewType.grid:
|
||||
child = DocumentGridView(
|
||||
onTap: _openDetails,
|
||||
state: state,
|
||||
onTap: _openDetails,
|
||||
onSelected: _onSelected,
|
||||
pagingController: _pagingController,
|
||||
hasInternetConnection: isConnected,
|
||||
onTagSelected: _addTagToFilter,
|
||||
onCorrespondentSelected: _addCorrespondentToFilter,
|
||||
onDocumentTypeSelected: _addDocumentTypeToFilter,
|
||||
onStoragePathSelected: _addStoragePathToFilter,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -171,7 +177,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
child: DocumentsEmptyState(
|
||||
state: state,
|
||||
onReset: () {
|
||||
_documentsCubit.updateFilter();
|
||||
_documentsCubit.resetFilter();
|
||||
_savedViewCubit.resetSelection();
|
||||
},
|
||||
),
|
||||
@@ -190,7 +196,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
listener: (context, state) {
|
||||
try {
|
||||
if (state.selectedSavedViewId == null) {
|
||||
_documentsCubit.updateFilter();
|
||||
_documentsCubit.resetFilter();
|
||||
} else {
|
||||
final newFilter = state
|
||||
.value[state.selectedSavedViewId]
|
||||
@@ -275,6 +281,63 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void _addCorrespondentToFilter(int? correspondentId) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == correspondentId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(correspondent: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
correspondent: IdQueryParameter.fromId(correspondentId)),
|
||||
);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _addDocumentTypeToFilter(int? documentTypeId) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.documentType.id == documentTypeId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(documentType: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
documentType: IdQueryParameter.fromId(documentTypeId)),
|
||||
);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _addStoragePathToFilter(int? pathId) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == pathId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(storagePath: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
||||
);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final pageCount = _documentsCubit.state
|
||||
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
|
||||
@@ -294,9 +357,10 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await _documentsCubit.updateCurrentFilter(
|
||||
_documentsCubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
_savedViewCubit.reload();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ class DocumentGridView extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
final bool hasInternetConnection;
|
||||
final void Function(int tagId) onTagSelected;
|
||||
final void Function(int correspondentId) onCorrespondentSelected;
|
||||
final void Function(int correspondentId) onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
const DocumentGridView({
|
||||
super.key,
|
||||
@@ -21,6 +24,9 @@ class DocumentGridView extends StatelessWidget {
|
||||
required this.onSelected,
|
||||
required this.hasInternetConnection,
|
||||
required this.onTagSelected,
|
||||
required this.onCorrespondentSelected,
|
||||
required this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -78,7 +78,7 @@ class DocumentGridItem extends StatelessWidget {
|
||||
DateFormat.yMMMd().format(
|
||||
document.created,
|
||||
),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
@@ -16,7 +15,10 @@ class DocumentListView extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
final bool hasInternetConnection;
|
||||
final bool isLabelClickable;
|
||||
final void Function(int tagId) onTagSelected;
|
||||
final void Function(int id)? onTagSelected;
|
||||
final void Function(int? id)? onCorrespondentSelected;
|
||||
final void Function(int? id)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
const DocumentListView({
|
||||
super.key,
|
||||
@@ -26,7 +28,10 @@ class DocumentListView extends StatelessWidget {
|
||||
required this.onSelected,
|
||||
required this.hasInternetConnection,
|
||||
this.isLabelClickable = true,
|
||||
required this.onTagSelected,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -52,6 +57,9 @@ class DocumentListView extends StatelessWidget {
|
||||
: false;
|
||||
},
|
||||
onTagSelected: onTagSelected,
|
||||
onCorrespondentSelected: onCorrespondentSelected,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -14,7 +14,10 @@ class DocumentListItem extends StatelessWidget {
|
||||
final bool isLabelClickable;
|
||||
final bool Function(int tagId) isTagSelectedPredicate;
|
||||
|
||||
final void Function(int tagId) onTagSelected;
|
||||
final void Function(int tagId)? onTagSelected;
|
||||
final void Function(int? correspondentId)? onCorrespondentSelected;
|
||||
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
||||
final void Function(int? id)? onStoragePathSelected;
|
||||
|
||||
const DocumentListItem({
|
||||
Key? key,
|
||||
@@ -25,7 +28,10 @@ class DocumentListItem extends StatelessWidget {
|
||||
required this.isAtLeastOneSelected,
|
||||
this.isLabelClickable = true,
|
||||
required this.isTagSelectedPredicate,
|
||||
required this.onTagSelected,
|
||||
this.onTagSelected,
|
||||
this.onCorrespondentSelected,
|
||||
this.onDocumentTypeSelected,
|
||||
this.onStoragePathSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -48,6 +54,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -68,7 +75,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
isSelectedPredicate: isTagSelectedPredicate,
|
||||
onTagSelected: onTagSelected,
|
||||
onTagSelected: (id) => onTagSelected?.call(id),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -73,6 +73,18 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
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,
|
||||
@@ -85,7 +97,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterSearchLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).paddedOnly(left: 8.0),
|
||||
_buildQueryFormField().padded(),
|
||||
@@ -93,7 +105,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentFilterAdvancedLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
).padded(),
|
||||
FormBuilderExtendedDateRangePicker(
|
||||
@@ -132,7 +144,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
|
||||
void _resetFilter() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(context, DocumentFilter.initial);
|
||||
Navigator.pop(
|
||||
context,
|
||||
DocumentFilter.initial.copyWith(
|
||||
sortField: widget.initialFilter.sortField,
|
||||
sortOrder: widget.initialFilter.sortOrder,
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
|
||||
@@ -47,7 +47,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).documentsPageOrderByLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
TextButton(
|
||||
|
||||
@@ -100,7 +100,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Label.isInsensitiveKey,
|
||||
initialValue: widget.initialValue?.isInsensitive,
|
||||
initialValue: widget.initialValue?.isInsensitive ?? true,
|
||||
title: Text(S.of(context).labelIsInsensivitePropertyLabel),
|
||||
),
|
||||
...widget.additionalFields,
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class InfoDrawer extends StatelessWidget {
|
||||
final VoidCallback? afterInboxClosed;
|
||||
@@ -31,6 +32,12 @@ class InfoDrawer extends StatelessWidget {
|
||||
bottomRight: Radius.circular(16.0),
|
||||
),
|
||||
child: Drawer(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(16.0),
|
||||
bottomRight: Radius.circular(16.0),
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
DrawerHeader(
|
||||
@@ -54,7 +61,8 @@ class InfoDrawer extends StatelessWidget {
|
||||
).paddedOnly(right: 8.0),
|
||||
Text(
|
||||
S.of(context).appTitleText,
|
||||
style: Theme.of(context).textTheme.headline5?.copyWith(
|
||||
style:
|
||||
Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
@@ -80,7 +88,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
title: Text(
|
||||
S.of(context).appDrawerHeaderLoggedInAsText +
|
||||
(info.username ?? '?'),
|
||||
style: Theme.of(context).textTheme.bodyText2,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
@@ -91,14 +99,15 @@ class InfoDrawer extends StatelessWidget {
|
||||
Text(
|
||||
state.information!.host ?? '',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyText2,
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
Text(
|
||||
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
@@ -118,12 +127,12 @@ class InfoDrawer extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
),
|
||||
...[
|
||||
ListTile(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
leading: const Icon(Icons.inbox),
|
||||
onTap: () => _onOpenInbox(context),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(
|
||||
@@ -138,7 +147,6 @@ class InfoDrawer extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bug_report),
|
||||
title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
@@ -147,7 +155,6 @@ class InfoDrawer extends StatelessWidget {
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
AboutListTile(
|
||||
icon: const Icon(Icons.info),
|
||||
applicationIcon: const ImageIcon(
|
||||
@@ -179,7 +186,6 @@ class InfoDrawer extends StatelessWidget {
|
||||
],
|
||||
child: Text(S.of(context).appDrawerAboutLabel),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||
@@ -188,10 +194,13 @@ class InfoDrawer extends StatelessWidget {
|
||||
BlocProvider.of<AuthenticationCubit>(context).logout();
|
||||
getIt<LocalVault>().clear();
|
||||
BlocProvider.of<ApplicationSettingsCubit>(context).clear();
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context).clear();
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context)
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context)
|
||||
.clear();
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context)
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context)
|
||||
.clear();
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context)
|
||||
.clear();
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context)
|
||||
.clear();
|
||||
@@ -200,8 +209,8 @@ class InfoDrawer extends StatelessWidget {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
)
|
||||
].expand((element) => [element, const Divider()]),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -53,7 +53,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
child: Text(
|
||||
'${state.inboxItems.length} ${S.of(context).inboxPageUnseenText}',
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).paddedSymmetrically(horizontal: 4.0),
|
||||
),
|
||||
),
|
||||
@@ -104,7 +104,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
).padded(),
|
||||
),
|
||||
|
||||
@@ -9,16 +9,18 @@ import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class CorrespondentWidget extends StatelessWidget {
|
||||
final int? correspondentId;
|
||||
final void Function()? afterSelected;
|
||||
final void Function(int? id)? onSelected;
|
||||
final Color? textColor;
|
||||
final bool isClickable;
|
||||
final TextStyle? textStyle;
|
||||
|
||||
const CorrespondentWidget({
|
||||
Key? key,
|
||||
this.correspondentId,
|
||||
this.afterSelected,
|
||||
required this.correspondentId,
|
||||
this.textColor,
|
||||
this.isClickable = true,
|
||||
this.textStyle,
|
||||
this.onSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -30,12 +32,13 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addCorrespondentToFilter(context),
|
||||
onTap: () => onSelected?.call(correspondentId!),
|
||||
child: Text(
|
||||
(state.getLabel(correspondentId)?.name) ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
||||
?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
@@ -45,24 +48,4 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addCorrespondentToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == correspondentId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(correspondent: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
correspondent: IdQueryParameter.fromId(correspondentId)),
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,20 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentTypeWidget extends StatelessWidget {
|
||||
final int? documentTypeId;
|
||||
final void Function()? afterSelected;
|
||||
final bool isClickable;
|
||||
final TextStyle? textStyle;
|
||||
final void Function(int? id)? onSelected;
|
||||
const DocumentTypeWidget({
|
||||
Key? key,
|
||||
required this.documentTypeId,
|
||||
this.afterSelected,
|
||||
this.isClickable = true,
|
||||
this.textStyle,
|
||||
this.onSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -27,16 +27,14 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => _addDocumentTypeToFilter(context),
|
||||
onTap: () => onSelected?.call(documentTypeId),
|
||||
child:
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.labels[documentTypeId]?.toString() ?? "-",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2!
|
||||
.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
||||
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -44,24 +42,4 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addDocumentTypeToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.documentType.id == documentTypeId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(documentType: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
documentType: IdQueryParameter.fromId(documentTypeId)),
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class _StoragePathAutofillFormBuilderFieldState
|
||||
const SizedBox(height: 8.0),
|
||||
Text(
|
||||
"Select to autofill path variable",
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
|
||||
@@ -2,23 +2,21 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class StoragePathWidget extends StatelessWidget {
|
||||
final int? pathId;
|
||||
final void Function()? afterSelected;
|
||||
final Color? textColor;
|
||||
final bool isClickable;
|
||||
final void Function(int? id)? onSelected;
|
||||
|
||||
const StoragePathWidget({
|
||||
Key? key,
|
||||
this.pathId,
|
||||
this.afterSelected,
|
||||
this.textColor,
|
||||
this.isClickable = true,
|
||||
this.onSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -32,12 +30,12 @@ class StoragePathWidget extends StatelessWidget {
|
||||
child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addStoragePathToFilter(context),
|
||||
onTap: () => onSelected?.call(pathId),
|
||||
child: Text(
|
||||
state.getLabel(pathId)?.name ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
@@ -47,24 +45,4 @@ class StoragePathWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addStoragePathToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == pathId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(storagePath: const IdQueryParameter.unset()),
|
||||
);
|
||||
} else {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,15 +85,18 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
|
||||
TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
|
||||
),
|
||||
),
|
||||
getImmediateSuggestions: true,
|
||||
loadingBuilder: (context) => Container(),
|
||||
initialValue: widget.initialValue ?? const IdQueryParameter.unset(),
|
||||
name: widget.name,
|
||||
suggestionsBoxDecoration: SuggestionsBoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2.0,
|
||||
),
|
||||
// side: BorderSide(
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// width: 2.0,
|
||||
// ),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, suggestion) => ListTile(
|
||||
@@ -103,7 +106,11 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
tileColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
dense: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
style: ListTileStyle.list,
|
||||
),
|
||||
suggestionsCallback: (pattern) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
||||
Text(
|
||||
S.of(context).referencedDocumentsReadOnlyHintText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -11,12 +10,9 @@ import 'package:paperless_mobile/features/login/model/user_credentials.model.dar
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@singleton
|
||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final LocalAuthenticationService _localAuthService;
|
||||
final PaperlessAuthenticationApi _authApi;
|
||||
PaperlessAuthenticationApi _authApi;
|
||||
final LocalVault _localVault;
|
||||
|
||||
AuthenticationCubit(
|
||||
@@ -25,10 +21,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
this._authApi,
|
||||
) : super(AuthenticationState.initial);
|
||||
|
||||
Future<void> initialize() {
|
||||
return restoreSessionState();
|
||||
}
|
||||
|
||||
Future<void> login({
|
||||
required UserCredentials credentials,
|
||||
required String serverUrl,
|
||||
@@ -36,7 +28,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
}) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
try {
|
||||
registerSecurityContext(clientCertificate);
|
||||
await registerSecurityContext(clientCertificate);
|
||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
||||
// Store information required to make requests
|
||||
final currentAuth = AuthenticationInformation(
|
||||
serverUrl: serverUrl,
|
||||
@@ -82,13 +76,16 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
}
|
||||
if (storedAuth == null || !storedAuth.isValid) {
|
||||
return emit(
|
||||
AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
AuthenticationState(isAuthenticated: false, wasLoginStored: false),
|
||||
);
|
||||
} else {
|
||||
if (appSettings.isLocalAuthenticationEnabled) {
|
||||
final localAuthSuccess = await _localAuthService
|
||||
.authenticateLocalUser("Authenticate to log back in");
|
||||
if (localAuthSuccess) {
|
||||
registerSecurityContext(storedAuth.clientCertificate);
|
||||
await registerSecurityContext(storedAuth.clientCertificate);
|
||||
//TODO: Workaround for new architecture, listen for security context changes in timeout_client, possibly persisted in hive.
|
||||
_authApi = getIt<PaperlessAuthenticationApi>();
|
||||
return emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
@@ -105,11 +102,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return emit(AuthenticationState(
|
||||
await registerSecurityContext(storedAuth.clientCertificate);
|
||||
final authState = AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
authentication: storedAuth,
|
||||
wasLoginStored: true,
|
||||
));
|
||||
);
|
||||
return emit(authState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
@@ -12,22 +11,26 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
|
||||
SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
|
||||
_subscription = _repository.savedViews.listen(
|
||||
(savedViews) => emit(state.copyWith(value: savedViews)),
|
||||
(savedViews) {
|
||||
if (savedViews == null) {
|
||||
emit(state.copyWith(isLoaded: false));
|
||||
} else {
|
||||
emit(state.copyWith(value: savedViews, isLoaded: true));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void selectView(SavedView? view) {
|
||||
emit(SavedViewState(value: state.value, selectedSavedViewId: view?.id));
|
||||
emit(state.copyWith(
|
||||
selectedSavedViewId: view?.id,
|
||||
overwriteSelectedSavedViewId: true,
|
||||
));
|
||||
}
|
||||
|
||||
Future<SavedView> add(SavedView view) async {
|
||||
final savedView = await _repository.create(view);
|
||||
emit(
|
||||
SavedViewState(
|
||||
value: {...state.value, savedView.id!: savedView},
|
||||
selectedSavedViewId: state.selectedSavedViewId,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(value: {...state.value, savedView.id!: savedView}));
|
||||
return savedView;
|
||||
}
|
||||
|
||||
@@ -42,11 +45,16 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
Future<void> initialize() async {
|
||||
final views = await _repository.findAll();
|
||||
final values = {for (var element in views) element.id!: element};
|
||||
emit(SavedViewState(value: values));
|
||||
emit(SavedViewState(value: values, isLoaded: true));
|
||||
}
|
||||
|
||||
Future<void> reload() => initialize();
|
||||
|
||||
void resetSelection() {
|
||||
emit(SavedViewState(value: state.value));
|
||||
emit(SavedViewState(
|
||||
value: state.value,
|
||||
isLoaded: true,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,11 +2,13 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
class SavedViewState with EquatableMixin {
|
||||
final bool isLoaded;
|
||||
final Map<int, SavedView> value;
|
||||
final int? selectedSavedViewId;
|
||||
|
||||
SavedViewState({
|
||||
required this.value,
|
||||
this.isLoaded = false,
|
||||
this.selectedSavedViewId,
|
||||
});
|
||||
|
||||
@@ -20,9 +22,11 @@ class SavedViewState with EquatableMixin {
|
||||
Map<int, SavedView>? value,
|
||||
int? selectedSavedViewId,
|
||||
bool overwriteSelectedSavedViewId = false,
|
||||
bool? isLoaded,
|
||||
}) {
|
||||
return SavedViewState(
|
||||
value: value ?? this.value,
|
||||
isLoaded: isLoaded ?? this.isLoaded,
|
||||
selectedSavedViewId: overwriteSelectedSavedViewId
|
||||
? selectedSavedViewId
|
||||
: this.selectedSavedViewId,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
@@ -8,6 +10,7 @@ import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class SavedViewSelectionWidget extends StatelessWidget {
|
||||
final DocumentFilter currentFilter;
|
||||
@@ -29,6 +32,9 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
children: [
|
||||
BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
return _buildLoadingWidget(context);
|
||||
}
|
||||
if (state.value.isEmpty) {
|
||||
return Text(S.of(context).savedViewsEmptyStateText);
|
||||
}
|
||||
@@ -58,7 +64,9 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
Row(
|
||||
BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
@@ -71,7 +79,7 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
builder: (context, docState) {
|
||||
return TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: enabled
|
||||
onPressed: (enabled && state.isLoaded)
|
||||
? () => _onCreatePressed(context, docState.filter)
|
||||
: null,
|
||||
label: Text(S.of(context).savedViewCreateNewLabel),
|
||||
@@ -79,11 +87,38 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingWidget(BuildContext context) {
|
||||
final r = Random(123456789);
|
||||
return SizedBox(
|
||||
height: height,
|
||||
width: double.infinity,
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[300]!
|
||||
: Colors.grey[900]!,
|
||||
highlightColor: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[100]!
|
||||
: Colors.grey[600]!,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 10,
|
||||
itemBuilder: (context, index) => FilterChip(
|
||||
label: SizedBox(width: r.nextInt((index * 20) + 50).toDouble()),
|
||||
onSelected: null),
|
||||
separatorBuilder: (context, index) => SizedBox(width: 8.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreatePressed(BuildContext context, DocumentFilter filter) async {
|
||||
final newView = await Navigator.of(context).push<SavedView?>(
|
||||
MaterialPageRoute(
|
||||
|
||||
@@ -84,7 +84,7 @@ class _GridImageItemWidgetState extends State<GridImageItemWidget> {
|
||||
),
|
||||
child: Text(
|
||||
"${widget.index + 1}/${widget.totalNumberOfFiles}",
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
120
lib/main.dart
120
lib/main.dart
@@ -1,3 +1,4 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -35,6 +36,7 @@ import 'package:paperless_mobile/features/document_upload/cubit/document_upload_
|
||||
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/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
@@ -58,7 +60,13 @@ void main() async {
|
||||
// Load application settings and stored authentication data
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
|
||||
final authCubit = AuthenticationCubit(
|
||||
getIt<LocalVault>(),
|
||||
getIt<LocalAuthenticationService>(),
|
||||
getIt<PaperlessAuthenticationApi>(),
|
||||
);
|
||||
await authCubit.restoreSessionState();
|
||||
|
||||
// Create repositories
|
||||
final LabelRepository<Tag> tagRepository =
|
||||
@@ -81,13 +89,17 @@ void main() async {
|
||||
RepositoryProvider.value(value: storagePathRepository),
|
||||
RepositoryProvider.value(value: savedViewRepository),
|
||||
],
|
||||
child: const PaperlessMobileEntrypoint(),
|
||||
child: PaperlessMobileEntrypoint(authenticationCubit: authCubit),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
const PaperlessMobileEntrypoint({Key? key}) : super(key: key);
|
||||
final AuthenticationCubit authenticationCubit;
|
||||
const PaperlessMobileEntrypoint({
|
||||
Key? key,
|
||||
required this.authenticationCubit,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PaperlessMobileEntrypoint> createState() =>
|
||||
@@ -95,6 +107,48 @@ class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
final _lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: Colors.lightGreen,
|
||||
appBarTheme: const AppBarTheme(
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.lightGreen[50],
|
||||
),
|
||||
);
|
||||
|
||||
final _darkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: Colors.lightGreen,
|
||||
appBarTheme: const AppBarTheme(
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.green[900],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
@@ -114,45 +168,13 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: Colors.lightGreen,
|
||||
appBarTheme: const AppBarTheme(
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.lightGreen[50],
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: Colors.lightGreen,
|
||||
appBarTheme: const AppBarTheme(
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.green[900],
|
||||
theme: _lightTheme.copyWith(
|
||||
listTileTheme: _lightTheme.listTileTheme
|
||||
.copyWith(tileColor: Colors.transparent),
|
||||
),
|
||||
darkTheme: _darkTheme.copyWith(
|
||||
listTileTheme: _darkTheme.listTileTheme
|
||||
.copyWith(tileColor: Colors.transparent),
|
||||
),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
@@ -166,8 +188,8 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
home: BlocProvider<AuthenticationCubit>.value(
|
||||
value: getIt<AuthenticationCubit>(),
|
||||
home: BlocProvider.value(
|
||||
value: widget.authenticationCubit,
|
||||
child: const AuthenticationWrapper(),
|
||||
),
|
||||
);
|
||||
@@ -270,7 +292,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
top: true,
|
||||
left: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
@@ -292,10 +314,10 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
if (authentication.isAuthenticated) {
|
||||
return const HomePage();
|
||||
} else {
|
||||
// if (authentication.wasLoginStored &&
|
||||
// !(authentication.wasLocalAuthenticationSuccessful ?? false)) {
|
||||
// return const BiometricAuthenticationPage();
|
||||
// }
|
||||
if (authentication.wasLoginStored &&
|
||||
!(authentication.wasLocalAuthenticationSuccessful ?? false)) {
|
||||
return const BiometricAuthenticationPage();
|
||||
}
|
||||
return const LoginPage();
|
||||
}
|
||||
},
|
||||
@@ -320,7 +342,7 @@ class BiometricAuthenticationPage extends StatelessWidget {
|
||||
Text(
|
||||
"You can now either try to authenticate again or disconnect from the current server.",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).padded(),
|
||||
const SizedBox(height: 48),
|
||||
Row(
|
||||
|
||||
@@ -261,7 +261,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
@override
|
||||
Future<StoragePath?> getStoragePath(int id) {
|
||||
return getSingleResult(
|
||||
"/api/storage_paths/?page=1&page_size=100000",
|
||||
"/api/storage_paths/$id/",
|
||||
StoragePath.fromJson,
|
||||
ErrorCode.storagePathLoadFailed,
|
||||
client: client,
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.3.0+9
|
||||
version: 1.3.1+10
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user