Migrated to flutter master channel, some adaptations to new m3 specs

This commit is contained in:
Anton Stubenbord
2022-12-20 00:27:59 +01:00
39 changed files with 554 additions and 442 deletions

View File

@@ -124,7 +124,13 @@ flutter install
``` ```
## Languages and Translations ## 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 -->
## Roadmap ## Roadmap

View File

@@ -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/core/store/local_vault.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/di_test_mocks.mocks.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/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart'; import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
@@ -49,7 +48,6 @@ void main() async {
await getIt<ConnectivityCubit>().initialize(); await getIt<ConnectivityCubit>().initialize();
await getIt<ApplicationSettingsCubit>().initialize(); await getIt<ApplicationSettingsCubit>().initialize();
await getIt<AuthenticationCubit>().initialize();
}); });
// Mocked classes // Mocked classes
@@ -97,7 +95,6 @@ void main() async {
await getIt<ConnectivityCubit>().initialize(); await getIt<ConnectivityCubit>().initialize();
await getIt<ApplicationSettingsCubit>().initialize(); await getIt<ApplicationSettingsCubit>().initialize();
await getIt<AuthenticationCubit>().initialize();
}); });
// Mocked classes // Mocked classes
@@ -149,7 +146,6 @@ void main() async {
)); ));
await getIt<ConnectivityCubit>().initialize(); await getIt<ConnectivityCubit>().initialize();
await getIt<ApplicationSettingsCubit>().initialize(); await getIt<ApplicationSettingsCubit>().initialize();
await getIt<AuthenticationCubit>().initialize();
}); });
await t.binding.waitUntilFirstFrameRasterized; await t.binding.waitUntilFirstFrameRasterized;
@@ -199,7 +195,6 @@ void main() async {
await getIt<ConnectivityCubit>().initialize(); await getIt<ConnectivityCubit>().initialize();
await getIt<ApplicationSettingsCubit>().initialize(); await getIt<ApplicationSettingsCubit>().initialize();
await getIt<AuthenticationCubit>().initialize();
}); });
await t.binding.waitUntilFirstFrameRasterized; await t.binding.waitUntilFirstFrameRasterized;

View File

@@ -3,7 +3,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/main.dart';
Future<TestingFrameworkVariables> initializeTestingFramework( Future<TestingFrameworkVariables> initializeTestingFramework(
{String languageCode = 'en'}) async { {String languageCode = 'en'}) async {
@@ -35,5 +34,5 @@ Future<void> initAndLaunchTestApp(
Future<void> Function() initializationCallback, Future<void> Function() initializationCallback,
) async { ) async {
await initializationCallback(); await initializationCallback();
runApp(const PaperlessMobileEntrypoint()); //runApp(const PaperlessMobileEntrypoint(authenticationCubit: ),));
} }

View File

@@ -1,5 +1,4 @@
import 'package:paperless_api/paperless_api.dart'; 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:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
@@ -8,20 +7,21 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
SavedViewRepositoryImpl(this._api); SavedViewRepositoryImpl(this._api);
final BehaviorSubject<Map<int, SavedView>> _subject = final BehaviorSubject<Map<int, SavedView>?> _subject = BehaviorSubject();
BehaviorSubject.seeded({});
@override @override
Stream<Map<int, SavedView>> get savedViews => Stream<Map<int, SavedView>?> get savedViews =>
_subject.stream.asBroadcastStream(); _subject.stream.asBroadcastStream();
@override @override
void clear() {} void clear() {
_subject.add(const {});
}
@override @override
Future<SavedView> create(SavedView view) async { Future<SavedView> create(SavedView view) async {
final created = await _api.save(view); final created = await _api.save(view);
final updatedState = {..._subject.value} final updatedState = {..._subject.valueOrNull ?? {}}
..putIfAbsent(created.id!, () => created); ..putIfAbsent(created.id!, () => created);
_subject.add(updatedState); _subject.add(updatedState);
return created; return created;
@@ -30,7 +30,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
@override @override
Future<int> delete(SavedView view) async { Future<int> delete(SavedView view) async {
await _api.delete(view); await _api.delete(view);
final updatedState = {..._subject.value}..remove(view.id); final updatedState = {..._subject.valueOrNull ?? {}}..remove(view.id);
_subject.add(updatedState); _subject.add(updatedState);
return view.id!; return view.id!;
} }
@@ -38,7 +38,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
@override @override
Future<SavedView?> find(int id) async { Future<SavedView?> find(int id) async {
final found = await _api.find(id); final found = await _api.find(id);
final updatedState = {..._subject.value} final updatedState = {..._subject.valueOrNull ?? {}}
..update(id, (_) => found, ifAbsent: () => found); ..update(id, (_) => found, ifAbsent: () => found);
_subject.add(updatedState); _subject.add(updatedState);
return found; return found;
@@ -48,7 +48,7 @@ class SavedViewRepositoryImpl implements SavedViewRepository {
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async { Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
final found = await _api.findAll(ids); final found = await _api.findAll(ids);
final updatedState = { final updatedState = {
..._subject.value, ..._subject.valueOrNull ?? {},
...{for (final view in found) view.id!: view}, ...{for (final view in found) view.id!: view},
}; };
_subject.add(updatedState); _subject.add(updatedState);

View File

@@ -1,4 +1,3 @@
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';

View File

@@ -1,7 +1,7 @@
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
abstract class SavedViewRepository { abstract class SavedViewRepository {
Stream<Map<int, SavedView>> get savedViews; Stream<Map<int, SavedView>?> get savedViews;
Future<SavedView> create(SavedView view); Future<SavedView> create(SavedView view);
Future<SavedView?> find(int id); Future<SavedView?> find(int id);

View File

@@ -45,7 +45,7 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
).padded(), ).padded(),
Text( Text(
'Description', 'Description',
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.titleMedium,
).padded(), ).padded(),
FormBuilderTextField( FormBuilderTextField(
name: shortDescriptionKey, name: shortDescriptionKey,
@@ -70,7 +70,7 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
children: [ children: [
Text( Text(
'Stack Trace', 'Stack Trace',
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.titleMedium,
).paddedOnly(top: 8.0, left: 8.0, right: 8.0), ).paddedOnly(top: 8.0, left: 8.0, right: 8.0),
TextButton.icon( TextButton.icon(
label: const Text('Copy'), label: const Text('Copy'),
@@ -81,11 +81,11 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
), ),
Text( 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!', '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(), ).padded(),
Text( Text(
widget.stackTrace.toString(), widget.stackTrace.toString(),
style: Theme.of(context).textTheme.overline, style: Theme.of(context).textTheme.bodySmall,
).padded(), ).padded(),
] ]
], ],

View File

@@ -1,15 +1,10 @@
import 'dart:developer';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.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:intl/intl.dart';
import 'package:paperless_api/paperless_api.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/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/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class ExtendedDateRangeDialog extends StatefulWidget { class ExtendedDateRangeDialog extends StatefulWidget {
final DateRangeQuery initialValue; final DateRangeQuery initialValue;
@@ -56,13 +51,13 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildDateRangeQueryTypeSelection(),
Text( Text(
"Hint: You can either specify absolute values by selecting concrete dates, or you can specify a time range relative to the current date.", "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, style: Theme.of(context).textTheme.bodySmall,
), ).paddedOnly(top: 8, bottom: 16),
_buildDateRangeQueryTypeSelection(),
const SizedBox(height: 16),
Builder( Builder(
builder: (context) { builder: (context) {
switch (_selectedDateRangeType) { switch (_selectedDateRangeType) {
@@ -106,21 +101,23 @@ class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
} }
Widget _buildDateRangeQueryTypeSelection() { Widget _buildDateRangeQueryTypeSelection() {
return Row( return SegmentedButton<DateRangeType>(
children: [ multiSelectionEnabled: false,
ChoiceChip( onSelectionChanged: (selection) =>
label: Text('Absolute'), setState(() => _selectedDateRangeType = selection.first),
selected: _selectedDateRangeType == DateRangeType.absolute, segments: [
onSelected: (value) => ButtonSegment(
setState(() => _selectedDateRangeType = DateRangeType.absolute), value: DateRangeType.absolute,
).paddedOnly(right: 8.0), enabled: true,
ChoiceChip( label: Text("Absolute"),
label: Text('Relative'), ),
selected: _selectedDateRangeType == DateRangeType.relative, ButtonSegment(
onSelected: (value) => value: DateRangeType.relative,
setState(() => _selectedDateRangeType = DateRangeType.relative), enabled: true,
label: Text("Relative"),
), ),
], ],
selected: {_selectedDateRangeType},
); );
} }

View File

@@ -97,15 +97,15 @@ class _FormBuilderRelativeDateRangePickerState
), ),
], ],
), ),
RelativeDateRangePickerHelper( // RelativeDateRangePickerHelper(
field: field, // field: field,
onChanged: (value) { // onChanged: (value) {
if (value is RelativeDateRangeQuery) { // if (value is RelativeDateRangeQuery) {
setState(() => _offset = value.offset); // setState(() => _offset = value.offset);
_offsetTextEditingController.text = _offset.toString(); // _offsetTextEditingController.text = _offset.toString();
} // }
}, // },
), // ),
], ],
), ),
); );

View File

@@ -121,7 +121,7 @@ class HighlightedText extends StatelessWidget {
TextSpan _normalSpan(String value, BuildContext context) { TextSpan _normalSpan(String value, BuildContext context) {
return TextSpan( return TextSpan(
text: value, text: value,
style: style ?? Theme.of(context).textTheme.bodyText2, style: style ?? Theme.of(context).textTheme.bodyMedium,
); );
} }
} }

View File

@@ -2,7 +2,6 @@ import 'dart:io';
import 'package:paperless_mobile/di_initializer.config.dart'; import 'package:paperless_mobile/di_initializer.config.dart';
import 'package:paperless_mobile/di_modules.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:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.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]. /// 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(); var context = SecurityContext();
if (cert != null) { if (cert != null) {
context = context context = context
@@ -29,6 +28,6 @@ void registerSecurityContext(ClientCertificate? cert) {
..useCertificateChainBytes(cert.bytes, password: cert.passphrase) ..useCertificateChainBytes(cert.bytes, password: cert.passphrase)
..setTrustedCertificatesBytes(cert.bytes, password: cert.passphrase); ..setTrustedCertificatesBytes(cert.bytes, password: cert.passphrase);
} }
getIt.unregister<SecurityContext>(); await getIt.unregister<SecurityContext>();
getIt.registerSingleton<SecurityContext>(context); getIt.registerSingleton<SecurityContext>(context);
} }

View File

@@ -45,8 +45,6 @@ class DocumentDetailsPage extends StatefulWidget {
} }
class _DocumentDetailsPageState extends State<DocumentDetailsPage> { class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
bool _isDownloadPending = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(
@@ -107,11 +105,11 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
color: Colors color: Colors
.black, //TODO: check if there is a way to dynamically determine color... .black, //TODO: check if there is a way to dynamically determine color...
), ),
onPressed: () => Navigator.pop( onPressed: () => Navigator.of(context).pop(
context, BlocProvider.of<DocumentDetailsCubit>(context)
BlocProvider.of<DocumentDetailsCubit>(context) .state
.state .document,
.document), ),
), ),
floating: true, floating: true,
pinned: true, pinned: true,
@@ -237,13 +235,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return ListView( return ListView(
children: [ children: [
_DetailsItem.text(DateFormat().format(document.modified), _DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel, label: S.of(context).documentModifiedPropertyLabel,
context: context), context: context)
_separator(), .paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added), _DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel, label: S.of(context).documentAddedPropertyLabel,
context: context), context: context)
_separator(), .paddedSymmetrically(vertical: 16),
_DetailsItem( _DetailsItem(
label: S.of(context).documentArchiveSerialNumberPropertyLongLabel, label: S.of(context).documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null content: document.archiveSerialNumber != null
@@ -255,30 +253,26 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
onPressed: onPressed:
widget.allowEdit ? () => _assignAsn(document) : null, widget.allowEdit ? () => _assignAsn(document) : null,
), ),
), ).paddedSymmetrically(vertical: 16),
_separator(),
_DetailsItem.text( _DetailsItem.text(
meta.mediaFilename, meta.mediaFilename,
context: context, context: context,
label: S.of(context).documentMetaDataMediaFilenamePropertyLabel, label: S.of(context).documentMetaDataMediaFilenamePropertyLabel,
), ).paddedSymmetrically(vertical: 16),
_separator(),
_DetailsItem.text( _DetailsItem.text(
meta.originalChecksum, meta.originalChecksum,
context: context, context: context,
label: S.of(context).documentMetaDataChecksumLabel, label: S.of(context).documentMetaDataChecksumLabel,
), ).paddedSymmetrically(vertical: 16),
_separator(),
_DetailsItem.text(formatBytes(meta.originalSize, 2), _DetailsItem.text(formatBytes(meta.originalSize, 2),
label: S.of(context).documentMetaDataOriginalFileSizeLabel, label: S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context), context: context)
_separator(), .paddedSymmetrically(vertical: 16),
_DetailsItem.text( _DetailsItem.text(
meta.originalMimeType, meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel, label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context, context: context,
), ).paddedSymmetrically(vertical: 16),
_separator(),
], ],
); );
}, },
@@ -295,16 +289,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
Widget _buildDocumentContentView(DocumentModel document, String? match) { Widget _buildDocumentContentView(DocumentModel document, String? match) {
return SingleChildScrollView( return SingleChildScrollView(
child: _DetailsItem( child: HighlightedText(
content: HighlightedText( text: document.content ?? "",
text: document.content ?? "", highlights: match == null ? [] : match.split(" "),
highlights: match == null ? [] : match.split(" "), style: Theme.of(context).textTheme.bodyMedium,
style: Theme.of(context).textTheme.bodyText2, caseSensitive: false,
caseSensitive: false,
),
label: S.of(context).documentDetailsPageTabContentLabel,
), ),
); ).paddedOnly(top: 8);
} }
Widget _buildDocumentOverview(DocumentModel document, String? match) { Widget _buildDocumentOverview(DocumentModel document, String? match) {
@@ -314,60 +305,61 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
content: HighlightedText( content: HighlightedText(
text: document.title, text: document.title,
highlights: match?.split(" ") ?? <String>[], highlights: match?.split(" ") ?? <String>[],
style: Theme.of(context).textTheme.bodyLarge,
), ),
label: S.of(context).documentTitlePropertyLabel, label: S.of(context).documentTitlePropertyLabel,
), ).paddedOnly(bottom: 16),
_separator(),
_DetailsItem.text( _DetailsItem.text(
DateFormat.yMMMd().format(document.created), DateFormat.yMMMd().format(document.created),
context: context, context: context,
label: S.of(context).documentCreatedPropertyLabel, label: S.of(context).documentCreatedPropertyLabel,
), ).paddedSymmetrically(vertical: 16),
_separator(), Visibility(
_DetailsItem( visible: document.documentType != null,
content: DocumentTypeWidget( child: _DetailsItem(
isClickable: widget.isLabelClickable, content: DocumentTypeWidget(
documentTypeId: document.documentType, textStyle: Theme.of(context).textTheme.bodyLarge,
afterSelected: () {
Navigator.pop(context);
},
),
label: S.of(context).documentDocumentTypePropertyLabel,
),
_separator(),
_DetailsItem(
label: S.of(context).documentCorrespondentPropertyLabel,
content: CorrespondentWidget(
isClickable: widget.isLabelClickable,
correspondentId: document.correspondent,
afterSelected: () {
Navigator.pop(context);
},
),
),
_separator(),
_DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
isClickable: widget.isLabelClickable,
pathId: document.storagePath,
afterSelected: () {
Navigator.pop(context);
},
),
),
_separator(),
_DetailsItem(
label: S.of(context).documentTagsPropertyLabel,
content: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget(
isClickable: widget.isLabelClickable, isClickable: widget.isLabelClickable,
tagIds: document.tags, documentTypeId: document.documentType,
isSelectedPredicate: (_) => false,
onTagSelected: (int tagId) {},
), ),
), label: S.of(context).documentDocumentTypePropertyLabel,
).paddedSymmetrically(vertical: 16),
),
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,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.storagePath != null,
child: _DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
isClickable: widget.isLabelClickable,
pathId: document.storagePath,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.tags.isNotEmpty,
child: _DetailsItem(
label: S.of(context).documentTagsPropertyLabel,
content: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget(
isClickable: widget.isLabelClickable,
tagIds: document.tags,
isSelectedPredicate: (_) => false,
onTagSelected: (int tagId) {},
),
),
).paddedSymmetrically(vertical: 16),
), ),
// _separator(), // _separator(),
// FutureBuilder<List<SimilarDocumentModel>>( // 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. /// Downloads file to temporary directory, from which it can then be shared.
/// ///
@@ -477,10 +465,7 @@ class _DetailsItem extends StatelessWidget {
children: [ children: [
Text( Text(
label, label,
style: Theme.of(context) style: Theme.of(context).textTheme.caption,
.textTheme
.headline5
?.copyWith(fontWeight: FontWeight.bold),
), ),
content, content,
], ],
@@ -492,7 +477,7 @@ class _DetailsItem extends StatelessWidget {
String text, { String text, {
required this.label, required this.label,
required BuildContext context, 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 { class ColoredTabBar extends Container implements PreferredSizeWidget {

View File

@@ -208,7 +208,7 @@ class _DocumentUploadPreparationPageState
S S
.of(context) .of(context)
.uploadPageAutomaticallInferredFieldsHintText, .uploadPageAutomaticallInferredFieldsHintText,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
].padded(), ].padded(),
), ),

View File

@@ -88,6 +88,14 @@ class DocumentsCubit extends Cubit<DocumentsState> {
emit(DocumentsState(filter: filter, value: [result], isLoaded: true)); 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. /// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
/// ///

View File

@@ -88,7 +88,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
isLabelVisible: appliedFiltersCount > 0, isLabelVisible: appliedFiltersCount > 0,
count: state.filter.appliedFiltersCount, count: state.filter.appliedFiltersCount,
child: FloatingActionButton( child: FloatingActionButton(
child: const Icon(Icons.filter_alt_rounded), child: const Icon(Icons.filter_alt_outlined),
onPressed: _openDocumentFilter, onPressed: _openDocumentFilter,
), ),
); );
@@ -146,22 +146,28 @@ class _DocumentsPageState extends State<DocumentsPage> {
switch (settings.preferredViewType) { switch (settings.preferredViewType) {
case ViewType.list: case ViewType.list:
child = DocumentListView( child = DocumentListView(
onTap: _openDetails,
state: state, state: state,
onTap: _openDetails,
onSelected: _onSelected, onSelected: _onSelected,
pagingController: _pagingController, pagingController: _pagingController,
hasInternetConnection: isConnected, hasInternetConnection: isConnected,
onTagSelected: _addTagToFilter, onTagSelected: _addTagToFilter,
onCorrespondentSelected: _addCorrespondentToFilter,
onDocumentTypeSelected: _addDocumentTypeToFilter,
onStoragePathSelected: _addStoragePathToFilter,
); );
break; break;
case ViewType.grid: case ViewType.grid:
child = DocumentGridView( child = DocumentGridView(
onTap: _openDetails,
state: state, state: state,
onTap: _openDetails,
onSelected: _onSelected, onSelected: _onSelected,
pagingController: _pagingController, pagingController: _pagingController,
hasInternetConnection: isConnected, hasInternetConnection: isConnected,
onTagSelected: _addTagToFilter, onTagSelected: _addTagToFilter,
onCorrespondentSelected: _addCorrespondentToFilter,
onDocumentTypeSelected: _addDocumentTypeToFilter,
onStoragePathSelected: _addStoragePathToFilter,
); );
break; break;
} }
@@ -171,7 +177,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
child: DocumentsEmptyState( child: DocumentsEmptyState(
state: state, state: state,
onReset: () { onReset: () {
_documentsCubit.updateFilter(); _documentsCubit.resetFilter();
_savedViewCubit.resetSelection(); _savedViewCubit.resetSelection();
}, },
), ),
@@ -190,7 +196,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
listener: (context, state) { listener: (context, state) {
try { try {
if (state.selectedSavedViewId == null) { if (state.selectedSavedViewId == null) {
_documentsCubit.updateFilter(); _documentsCubit.resetFilter();
} else { } else {
final newFilter = state final newFilter = state
.value[state.selectedSavedViewId] .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 { Future<void> _loadNewPage(int pageKey) async {
final pageCount = _documentsCubit.state final pageCount = _documentsCubit.state
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize); .inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
@@ -294,9 +357,10 @@ class _DocumentsPageState extends State<DocumentsPage> {
Future<void> _onRefresh() async { Future<void> _onRefresh() async {
try { try {
await _documentsCubit.updateCurrentFilter( _documentsCubit.updateCurrentFilter(
(filter) => filter.copyWith(page: 1), (filter) => filter.copyWith(page: 1),
); );
_savedViewCubit.reload();
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }

View File

@@ -12,6 +12,9 @@ class DocumentGridView extends StatelessWidget {
final DocumentsState state; final DocumentsState state;
final bool hasInternetConnection; final bool hasInternetConnection;
final void Function(int tagId) onTagSelected; 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({ const DocumentGridView({
super.key, super.key,
@@ -21,6 +24,9 @@ class DocumentGridView extends StatelessWidget {
required this.onSelected, required this.onSelected,
required this.hasInternetConnection, required this.hasInternetConnection,
required this.onTagSelected, required this.onTagSelected,
required this.onCorrespondentSelected,
required this.onDocumentTypeSelected,
this.onStoragePathSelected,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -78,7 +78,7 @@ class DocumentGridItem extends StatelessWidget {
DateFormat.yMMMd().format( DateFormat.yMMMd().format(
document.created, document.created,
), ),
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
], ],
), ),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart'; import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart'; import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
@@ -16,7 +15,10 @@ class DocumentListView extends StatelessWidget {
final DocumentsState state; final DocumentsState state;
final bool hasInternetConnection; final bool hasInternetConnection;
final bool isLabelClickable; 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({ const DocumentListView({
super.key, super.key,
@@ -26,7 +28,10 @@ class DocumentListView extends StatelessWidget {
required this.onSelected, required this.onSelected,
required this.hasInternetConnection, required this.hasInternetConnection,
this.isLabelClickable = true, this.isLabelClickable = true,
required this.onTagSelected, this.onTagSelected,
this.onCorrespondentSelected,
this.onDocumentTypeSelected,
this.onStoragePathSelected,
}); });
@override @override
@@ -52,6 +57,9 @@ class DocumentListView extends StatelessWidget {
: false; : false;
}, },
onTagSelected: onTagSelected, onTagSelected: onTagSelected,
onCorrespondentSelected: onCorrespondentSelected,
onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected,
), ),
); );
}, },

View File

@@ -14,7 +14,10 @@ class DocumentListItem extends StatelessWidget {
final bool isLabelClickable; final bool isLabelClickable;
final bool Function(int tagId) isTagSelectedPredicate; 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({ const DocumentListItem({
Key? key, Key? key,
@@ -25,7 +28,10 @@ class DocumentListItem extends StatelessWidget {
required this.isAtLeastOneSelected, required this.isAtLeastOneSelected,
this.isLabelClickable = true, this.isLabelClickable = true,
required this.isTagSelectedPredicate, required this.isTagSelectedPredicate,
required this.onTagSelected, this.onTagSelected,
this.onCorrespondentSelected,
this.onDocumentTypeSelected,
this.onStoragePathSelected,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -48,6 +54,7 @@ class DocumentListItem extends StatelessWidget {
child: CorrespondentWidget( child: CorrespondentWidget(
isClickable: isLabelClickable, isClickable: isLabelClickable,
correspondentId: document.correspondent, correspondentId: document.correspondent,
onSelected: onCorrespondentSelected,
), ),
), ),
], ],
@@ -68,7 +75,7 @@ class DocumentListItem extends StatelessWidget {
tagIds: document.tags, tagIds: document.tags,
isMultiLine: false, isMultiLine: false,
isSelectedPredicate: isTagSelectedPredicate, isSelectedPredicate: isTagSelectedPredicate,
onTagSelected: onTagSelected, onTagSelected: (id) => onTagSelected?.call(id),
), ),
), ),
), ),

View File

@@ -73,6 +73,18 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
child: ListView( child: ListView(
controller: widget.scrollController, controller: widget.scrollController,
children: [ 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( Text(
S.of(context).documentFilterTitle, S.of(context).documentFilterTitle,
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
@@ -85,7 +97,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
S.of(context).documentFilterSearchLabel, S.of(context).documentFilterSearchLabel,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
).paddedOnly(left: 8.0), ).paddedOnly(left: 8.0),
_buildQueryFormField().padded(), _buildQueryFormField().padded(),
@@ -93,7 +105,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
S.of(context).documentFilterAdvancedLabel, S.of(context).documentFilterAdvancedLabel,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
).padded(), ).padded(),
FormBuilderExtendedDateRangePicker( FormBuilderExtendedDateRangePicker(
@@ -132,7 +144,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
void _resetFilter() async { void _resetFilter() async {
FocusScope.of(context).unfocus(); 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() { Widget _buildDocumentTypeFormField() {

View File

@@ -47,7 +47,7 @@ class _SortFieldSelectionBottomSheetState
children: [ children: [
Text( Text(
S.of(context).documentsPageOrderByLabel, S.of(context).documentsPageOrderByLabel,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.start, textAlign: TextAlign.start,
), ),
TextButton( TextButton(

View File

@@ -100,7 +100,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
), ),
FormBuilderCheckbox( FormBuilderCheckbox(
name: Label.isInsensitiveKey, name: Label.isInsensitiveKey,
initialValue: widget.initialValue?.isInsensitive, initialValue: widget.initialValue?.isInsensitive ?? true,
title: Text(S.of(context).labelIsInsensivitePropertyLabel), title: Text(S.of(context).labelIsInsensivitePropertyLabel),
), ),
...widget.additionalFields, ...widget.additionalFields,

View File

@@ -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/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/repository/label_repository.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/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/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';

View File

@@ -18,6 +18,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
import 'package:url_launcher/link.dart'; import 'package:url_launcher/link.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:collection/collection.dart';
class InfoDrawer extends StatelessWidget { class InfoDrawer extends StatelessWidget {
final VoidCallback? afterInboxClosed; final VoidCallback? afterInboxClosed;
@@ -31,6 +32,12 @@ class InfoDrawer extends StatelessWidget {
bottomRight: Radius.circular(16.0), bottomRight: Radius.circular(16.0),
), ),
child: Drawer( child: Drawer(
shape: const RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
),
child: ListView( child: ListView(
children: [ children: [
DrawerHeader( DrawerHeader(
@@ -54,11 +61,12 @@ class InfoDrawer extends StatelessWidget {
).paddedOnly(right: 8.0), ).paddedOnly(right: 8.0),
Text( Text(
S.of(context).appTitleText, S.of(context).appTitleText,
style: Theme.of(context).textTheme.headline5?.copyWith( style:
color: Theme.of(context) Theme.of(context).textTheme.headlineSmall?.copyWith(
.colorScheme color: Theme.of(context)
.onPrimaryContainer, .colorScheme
), .onPrimaryContainer,
),
), ),
], ],
), ),
@@ -80,7 +88,7 @@ class InfoDrawer extends StatelessWidget {
title: Text( title: Text(
S.of(context).appDrawerHeaderLoggedInAsText + S.of(context).appDrawerHeaderLoggedInAsText +
(info.username ?? '?'), (info.username ?? '?'),
style: Theme.of(context).textTheme.bodyText2, style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, textAlign: TextAlign.end,
maxLines: 1, maxLines: 1,
@@ -91,14 +99,15 @@ class InfoDrawer extends StatelessWidget {
Text( Text(
state.information!.host ?? '', state.information!.host ?? '',
style: style:
Theme.of(context).textTheme.bodyText2, Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, textAlign: TextAlign.end,
maxLines: 1, maxLines: 1,
), ),
Text( Text(
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})', '${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, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, textAlign: TextAlign.end,
maxLines: 1, maxLines: 1,
@@ -118,90 +127,90 @@ class InfoDrawer extends StatelessWidget {
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
), ),
), ),
ListTile( ...[
title: Text(S.of(context).bottomNavInboxPageLabel), ListTile(
leading: const Icon(Icons.inbox), title: Text(S.of(context).bottomNavInboxPageLabel),
onTap: () => _onOpenInbox(context), leading: const Icon(Icons.inbox),
), onTap: () => _onOpenInbox(context),
const Divider(),
ListTile(
leading: const Icon(Icons.settings),
title: Text(
S.of(context).appDrawerSettingsLabel,
), ),
onTap: () => Navigator.of(context).push( ListTile(
MaterialPageRoute( leading: const Icon(Icons.settings),
builder: (context) => BlocProvider.value( title: Text(
value: getIt<ApplicationSettingsCubit>(), S.of(context).appDrawerSettingsLabel,
child: const SettingsPage(),
),
), ),
), onTap: () => Navigator.of(context).push(
), MaterialPageRoute(
const Divider(), builder: (context) => BlocProvider.value(
ListTile( value: getIt<ApplicationSettingsCubit>(),
leading: const Icon(Icons.bug_report), child: const SettingsPage(),
title: Text(S.of(context).appDrawerReportBugLabel),
onTap: () {
launchUrlString(
'https://github.com/astubenbord/paperless-mobile/issues/new');
},
),
const Divider(),
AboutListTile(
icon: const Icon(Icons.info),
applicationIcon: const ImageIcon(
AssetImage('assets/logos/paperless_logo_green.png')),
applicationName: 'Paperless Mobile',
applicationVersion:
kPackageInfo.version + '+' + kPackageInfo.buildNumber,
aboutBoxChildren: [
Text(
'${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
Link(
uri: Uri.parse(
'https://github.com/astubenbord/paperless-mobile'),
builder: (context, followLink) => GestureDetector(
onTap: followLink,
child: Text(
'https://github.com/astubenbord/paperless-mobile',
style: TextStyle(
color: Theme.of(context).colorScheme.tertiary),
), ),
), ),
), ),
const SizedBox(height: 16), ),
Text( ListTile(
'Credits', leading: const Icon(Icons.bug_report),
style: Theme.of(context).textTheme.titleMedium, title: Text(S.of(context).appDrawerReportBugLabel),
), onTap: () {
_buildOnboardingImageCredits(), launchUrlString(
], 'https://github.com/astubenbord/paperless-mobile/issues/new');
child: Text(S.of(context).appDrawerAboutLabel), },
), ),
const Divider(), AboutListTile(
ListTile( icon: const Icon(Icons.info),
leading: const Icon(Icons.logout), applicationIcon: const ImageIcon(
title: Text(S.of(context).appDrawerLogoutLabel), AssetImage('assets/logos/paperless_logo_green.png')),
onTap: () { applicationName: 'Paperless Mobile',
try { applicationVersion:
BlocProvider.of<AuthenticationCubit>(context).logout(); kPackageInfo.version + '+' + kPackageInfo.buildNumber,
getIt<LocalVault>().clear(); aboutBoxChildren: [
BlocProvider.of<ApplicationSettingsCubit>(context).clear(); Text(
RepositoryProvider.of<LabelRepository<Tag>>(context).clear(); '${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
RepositoryProvider.of<LabelRepository<Correspondent>>(context) Link(
.clear(); uri: Uri.parse(
RepositoryProvider.of<LabelRepository<DocumentType>>(context) 'https://github.com/astubenbord/paperless-mobile'),
.clear(); builder: (context, followLink) => GestureDetector(
RepositoryProvider.of<LabelRepository<StoragePath>>(context) onTap: followLink,
.clear(); child: Text(
RepositoryProvider.of<SavedViewRepository>(context).clear(); 'https://github.com/astubenbord/paperless-mobile',
} on PaperlessServerException catch (error, stackTrace) { style: TextStyle(
showErrorMessage(context, error, stackTrace); color: Theme.of(context).colorScheme.tertiary),
} ),
}, ),
), ),
const Divider(), const SizedBox(height: 16),
Text(
'Credits',
style: Theme.of(context).textTheme.titleMedium,
),
_buildOnboardingImageCredits(),
],
child: Text(S.of(context).appDrawerAboutLabel),
),
ListTile(
leading: const Icon(Icons.logout),
title: Text(S.of(context).appDrawerLogoutLabel),
onTap: () {
try {
BlocProvider.of<AuthenticationCubit>(context).logout();
getIt<LocalVault>().clear();
BlocProvider.of<ApplicationSettingsCubit>(context).clear();
RepositoryProvider.of<LabelRepository<Tag>>(context)
.clear();
RepositoryProvider.of<LabelRepository<Correspondent>>(
context)
.clear();
RepositoryProvider.of<LabelRepository<DocumentType>>(
context)
.clear();
RepositoryProvider.of<LabelRepository<StoragePath>>(context)
.clear();
RepositoryProvider.of<SavedViewRepository>(context).clear();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
},
)
].expand((element) => [element, const Divider()]),
], ],
), ),
), ),

View File

@@ -53,7 +53,7 @@ class _InboxPageState extends State<InboxPage> {
child: Text( child: Text(
'${state.inboxItems.length} ${S.of(context).inboxPageUnseenText}', '${state.inboxItems.length} ${S.of(context).inboxPageUnseenText}',
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
).paddedSymmetrically(horizontal: 4.0), ).paddedSymmetrically(horizontal: 4.0),
), ),
), ),
@@ -104,7 +104,7 @@ class _InboxPageState extends State<InboxPage> {
borderRadius: BorderRadius.circular(32.0), borderRadius: BorderRadius.circular(32.0),
child: Text( child: Text(
entry.key, entry.key,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center, textAlign: TextAlign.center,
).padded(), ).padded(),
), ),

View File

@@ -9,16 +9,18 @@ import 'package:paperless_mobile/util.dart';
class CorrespondentWidget extends StatelessWidget { class CorrespondentWidget extends StatelessWidget {
final int? correspondentId; final int? correspondentId;
final void Function()? afterSelected; final void Function(int? id)? onSelected;
final Color? textColor; final Color? textColor;
final bool isClickable; final bool isClickable;
final TextStyle? textStyle;
const CorrespondentWidget({ const CorrespondentWidget({
Key? key, Key? key,
this.correspondentId, required this.correspondentId,
this.afterSelected,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
this.textStyle,
this.onSelected,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -30,14 +32,15 @@ class CorrespondentWidget extends StatelessWidget {
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>( BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
builder: (context, state) { builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => _addCorrespondentToFilter(context), onTap: () => onSelected?.call(correspondentId!),
child: Text( child: Text(
(state.getLabel(correspondentId)?.name) ?? "-", (state.getLabel(correspondentId)?.name) ?? "-",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith( style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
color: textColor ?? Theme.of(context).colorScheme.primary, ?.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);
}
}
} }

View File

@@ -2,20 +2,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.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_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart'; import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/util.dart';
class DocumentTypeWidget extends StatelessWidget { class DocumentTypeWidget extends StatelessWidget {
final int? documentTypeId; final int? documentTypeId;
final void Function()? afterSelected;
final bool isClickable; final bool isClickable;
final TextStyle? textStyle;
final void Function(int? id)? onSelected;
const DocumentTypeWidget({ const DocumentTypeWidget({
Key? key, Key? key,
required this.documentTypeId, required this.documentTypeId,
this.afterSelected,
this.isClickable = true, this.isClickable = true,
this.textStyle,
this.onSelected,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -27,16 +27,14 @@ class DocumentTypeWidget extends StatelessWidget {
child: AbsorbPointer( child: AbsorbPointer(
absorbing: !isClickable, absorbing: !isClickable,
child: GestureDetector( child: GestureDetector(
onTap: () => _addDocumentTypeToFilter(context), onTap: () => onSelected?.call(documentTypeId),
child: child:
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>( BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
builder: (context, state) { builder: (context, state) {
return Text( return Text(
state.labels[documentTypeId]?.toString() ?? "-", state.labels[documentTypeId]?.toString() ?? "-",
style: Theme.of(context) style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
.textTheme ?.copyWith(color: Theme.of(context).colorScheme.tertiary),
.bodyText2!
.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);
}
}
} }

View File

@@ -60,7 +60,7 @@ class _StoragePathAutofillFormBuilderFieldState
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Text( Text(
"Select to autofill path variable", "Select to autofill path variable",
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
Wrap( Wrap(
alignment: WrapAlignment.start, alignment: WrapAlignment.start,

View File

@@ -2,23 +2,21 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.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_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart'; import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/util.dart';
class StoragePathWidget extends StatelessWidget { class StoragePathWidget extends StatelessWidget {
final int? pathId; final int? pathId;
final void Function()? afterSelected;
final Color? textColor; final Color? textColor;
final bool isClickable; final bool isClickable;
final void Function(int? id)? onSelected;
const StoragePathWidget({ const StoragePathWidget({
Key? key, Key? key,
this.pathId, this.pathId,
this.afterSelected,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
this.onSelected,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -32,12 +30,12 @@ class StoragePathWidget extends StatelessWidget {
child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>( child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
builder: (context, state) { builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => _addStoragePathToFilter(context), onTap: () => onSelected?.call(pathId),
child: Text( child: Text(
state.getLabel(pathId)?.name ?? "-", state.getLabel(pathId)?.name ?? "-",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: textColor ?? Theme.of(context).colorScheme.primary, 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);
}
}
} }

View File

@@ -85,15 +85,18 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0), TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
), ),
), ),
getImmediateSuggestions: true,
loadingBuilder: (context) => Container(),
initialValue: widget.initialValue ?? const IdQueryParameter.unset(), initialValue: widget.initialValue ?? const IdQueryParameter.unset(),
name: widget.name, name: widget.name,
suggestionsBoxDecoration: SuggestionsBoxDecoration( suggestionsBoxDecoration: SuggestionsBoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: BorderSide( // side: BorderSide(
color: Theme.of(context).colorScheme.primary, // color: Theme.of(context).colorScheme.primary,
width: 2.0, // width: 2.0,
), // ),
), ),
), ),
itemBuilder: (context, suggestion) => ListTile( itemBuilder: (context, suggestion) => ListTile(
@@ -103,7 +106,11 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
tileColor: Theme.of(context).colorScheme.surfaceVariant,
dense: true, dense: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
style: ListTileStyle.list, style: ListTileStyle.list,
), ),
suggestionsCallback: (pattern) { suggestionsCallback: (pattern) {

View File

@@ -46,7 +46,7 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
Text( Text(
S.of(context).referencedDocumentsReadOnlyHintText, S.of(context).referencedDocumentsReadOnlyHintText,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
Expanded( Expanded(
child: CustomScrollView( child: CustomScrollView(

View File

@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/store/local_vault.dart'; import 'package:paperless_mobile/core/store/local_vault.dart';
import 'package:paperless_mobile/di_initializer.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/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart'; import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
@prod
@test
@singleton
class AuthenticationCubit extends Cubit<AuthenticationState> { class AuthenticationCubit extends Cubit<AuthenticationState> {
final LocalAuthenticationService _localAuthService; final LocalAuthenticationService _localAuthService;
final PaperlessAuthenticationApi _authApi; PaperlessAuthenticationApi _authApi;
final LocalVault _localVault; final LocalVault _localVault;
AuthenticationCubit( AuthenticationCubit(
@@ -25,10 +21,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
this._authApi, this._authApi,
) : super(AuthenticationState.initial); ) : super(AuthenticationState.initial);
Future<void> initialize() {
return restoreSessionState();
}
Future<void> login({ Future<void> login({
required UserCredentials credentials, required UserCredentials credentials,
required String serverUrl, required String serverUrl,
@@ -36,7 +28,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
}) async { }) async {
assert(credentials.username != null && credentials.password != null); assert(credentials.username != null && credentials.password != null);
try { 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 // Store information required to make requests
final currentAuth = AuthenticationInformation( final currentAuth = AuthenticationInformation(
serverUrl: serverUrl, serverUrl: serverUrl,
@@ -82,13 +76,16 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
} }
if (storedAuth == null || !storedAuth.isValid) { if (storedAuth == null || !storedAuth.isValid) {
return emit( return emit(
AuthenticationState(isAuthenticated: false, wasLoginStored: false)); AuthenticationState(isAuthenticated: false, wasLoginStored: false),
);
} else { } else {
if (appSettings.isLocalAuthenticationEnabled) { if (appSettings.isLocalAuthenticationEnabled) {
final localAuthSuccess = await _localAuthService final localAuthSuccess = await _localAuthService
.authenticateLocalUser("Authenticate to log back in"); .authenticateLocalUser("Authenticate to log back in");
if (localAuthSuccess) { 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( return emit(
AuthenticationState( AuthenticationState(
isAuthenticated: true, isAuthenticated: true,
@@ -105,11 +102,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
)); ));
} }
} else { } else {
return emit(AuthenticationState( await registerSecurityContext(storedAuth.clientCertificate);
final authState = AuthenticationState(
isAuthenticated: true, isAuthenticated: true,
authentication: storedAuth, authentication: storedAuth,
wasLoginStored: true, wasLoginStored: true,
)); );
return emit(authState);
} }
} }
} }

View File

@@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.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: {})) { SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
_subscription = _repository.savedViews.listen( _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) { 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 { Future<SavedView> add(SavedView view) async {
final savedView = await _repository.create(view); final savedView = await _repository.create(view);
emit( emit(state.copyWith(value: {...state.value, savedView.id!: savedView}));
SavedViewState(
value: {...state.value, savedView.id!: savedView},
selectedSavedViewId: state.selectedSavedViewId,
),
);
return savedView; return savedView;
} }
@@ -42,11 +45,16 @@ class SavedViewCubit extends Cubit<SavedViewState> {
Future<void> initialize() async { Future<void> initialize() async {
final views = await _repository.findAll(); final views = await _repository.findAll();
final values = {for (var element in views) element.id!: element}; 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() { void resetSelection() {
emit(SavedViewState(value: state.value)); emit(SavedViewState(
value: state.value,
isLoaded: true,
));
} }
@override @override

View File

@@ -2,11 +2,13 @@ import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
class SavedViewState with EquatableMixin { class SavedViewState with EquatableMixin {
final bool isLoaded;
final Map<int, SavedView> value; final Map<int, SavedView> value;
final int? selectedSavedViewId; final int? selectedSavedViewId;
SavedViewState({ SavedViewState({
required this.value, required this.value,
this.isLoaded = false,
this.selectedSavedViewId, this.selectedSavedViewId,
}); });
@@ -20,9 +22,11 @@ class SavedViewState with EquatableMixin {
Map<int, SavedView>? value, Map<int, SavedView>? value,
int? selectedSavedViewId, int? selectedSavedViewId,
bool overwriteSelectedSavedViewId = false, bool overwriteSelectedSavedViewId = false,
bool? isLoaded,
}) { }) {
return SavedViewState( return SavedViewState(
value: value ?? this.value, value: value ?? this.value,
isLoaded: isLoaded ?? this.isLoaded,
selectedSavedViewId: overwriteSelectedSavedViewId selectedSavedViewId: overwriteSelectedSavedViewId
? selectedSavedViewId ? selectedSavedViewId
: this.selectedSavedViewId, : this.selectedSavedViewId,

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.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/features/saved_view/view/add_saved_view_page.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
import 'package:shimmer/shimmer.dart';
class SavedViewSelectionWidget extends StatelessWidget { class SavedViewSelectionWidget extends StatelessWidget {
final DocumentFilter currentFilter; final DocumentFilter currentFilter;
@@ -29,6 +32,9 @@ class SavedViewSelectionWidget extends StatelessWidget {
children: [ children: [
BlocBuilder<SavedViewCubit, SavedViewState>( BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) { builder: (context, state) {
if (!state.isLoaded) {
return _buildLoadingWidget(context);
}
if (state.value.isEmpty) { if (state.value.isEmpty) {
return Text(S.of(context).savedViewsEmptyStateText); return Text(S.of(context).savedViewsEmptyStateText);
} }
@@ -58,32 +64,61 @@ class SavedViewSelectionWidget extends StatelessWidget {
); );
}, },
), ),
Row( BlocBuilder<SavedViewCubit, SavedViewState>(
mainAxisAlignment: MainAxisAlignment.spaceBetween, builder: (context, state) {
children: [ return Row(
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
S.of(context).savedViewsLabel, children: [
style: Theme.of(context).textTheme.titleSmall, Text(
), S.of(context).savedViewsLabel,
BlocBuilder<DocumentsCubit, DocumentsState>( style: Theme.of(context).textTheme.titleSmall,
buildWhen: (previous, current) => ),
previous.filter != current.filter, BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, docState) { buildWhen: (previous, current) =>
return TextButton.icon( previous.filter != current.filter,
icon: const Icon(Icons.add), builder: (context, docState) {
onPressed: enabled return TextButton.icon(
? () => _onCreatePressed(context, docState.filter) icon: const Icon(Icons.add),
: null, onPressed: (enabled && state.isLoaded)
label: Text(S.of(context).savedViewCreateNewLabel), ? () => _onCreatePressed(context, docState.filter)
); : null,
}, label: Text(S.of(context).savedViewCreateNewLabel),
), );
], },
),
],
);
},
), ),
], ],
); );
} }
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 { void _onCreatePressed(BuildContext context, DocumentFilter filter) async {
final newView = await Navigator.of(context).push<SavedView?>( final newView = await Navigator.of(context).push<SavedView?>(
MaterialPageRoute( MaterialPageRoute(

View File

@@ -84,7 +84,7 @@ class _GridImageItemWidgetState extends State<GridImageItemWidget> {
), ),
child: Text( child: Text(
"${widget.index + 1}/${widget.totalNumberOfFiles}", "${widget.index + 1}/${widget.totalNumberOfFiles}",
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
), ),
), ),
), ),

View File

@@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:async'; import 'dart:async';
import 'dart:io'; 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/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/home_page.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.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/login/view/login_page.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_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/application_settings_state.dart';
@@ -58,7 +60,13 @@ void main() async {
// Load application settings and stored authentication data // Load application settings and stored authentication data
await getIt<ConnectivityCubit>().initialize(); await getIt<ConnectivityCubit>().initialize();
await getIt<ApplicationSettingsCubit>().initialize(); await getIt<ApplicationSettingsCubit>().initialize();
await getIt<AuthenticationCubit>().initialize();
final authCubit = AuthenticationCubit(
getIt<LocalVault>(),
getIt<LocalAuthenticationService>(),
getIt<PaperlessAuthenticationApi>(),
);
await authCubit.restoreSessionState();
// Create repositories // Create repositories
final LabelRepository<Tag> tagRepository = final LabelRepository<Tag> tagRepository =
@@ -81,13 +89,17 @@ void main() async {
RepositoryProvider.value(value: storagePathRepository), RepositoryProvider.value(value: storagePathRepository),
RepositoryProvider.value(value: savedViewRepository), RepositoryProvider.value(value: savedViewRepository),
], ],
child: const PaperlessMobileEntrypoint(), child: PaperlessMobileEntrypoint(authenticationCubit: authCubit),
), ),
); );
} }
class PaperlessMobileEntrypoint extends StatefulWidget { 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 @override
State<PaperlessMobileEntrypoint> createState() => State<PaperlessMobileEntrypoint> createState() =>
@@ -95,6 +107,48 @@ class PaperlessMobileEntrypoint extends StatefulWidget {
} }
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@@ -114,45 +168,13 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: true, debugShowCheckedModeBanner: true,
title: "Paperless Mobile", title: "Paperless Mobile",
theme: ThemeData( theme: _lightTheme.copyWith(
brightness: Brightness.light, listTileTheme: _lightTheme.listTileTheme
useMaterial3: true, .copyWith(tileColor: Colors.transparent),
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( darkTheme: _darkTheme.copyWith(
brightness: Brightness.dark, listTileTheme: _darkTheme.listTileTheme
useMaterial3: true, .copyWith(tileColor: Colors.transparent),
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],
),
), ),
themeMode: settings.preferredThemeMode, themeMode: settings.preferredThemeMode,
supportedLocales: S.delegate.supportedLocales, supportedLocales: S.delegate.supportedLocales,
@@ -166,8 +188,8 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
FormBuilderLocalizations.delegate, FormBuilderLocalizations.delegate,
], ],
home: BlocProvider<AuthenticationCubit>.value( home: BlocProvider.value(
value: getIt<AuthenticationCubit>(), value: widget.authenticationCubit,
child: const AuthenticationWrapper(), child: const AuthenticationWrapper(),
), ),
); );
@@ -270,7 +292,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
top: false, top: true,
left: false, left: false,
right: false, right: false,
bottom: false, bottom: false,
@@ -292,10 +314,10 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
if (authentication.isAuthenticated) { if (authentication.isAuthenticated) {
return const HomePage(); return const HomePage();
} else { } else {
// if (authentication.wasLoginStored && if (authentication.wasLoginStored &&
// !(authentication.wasLocalAuthenticationSuccessful ?? false)) { !(authentication.wasLocalAuthenticationSuccessful ?? false)) {
// return const BiometricAuthenticationPage(); return const BiometricAuthenticationPage();
// } }
return const LoginPage(); return const LoginPage();
} }
}, },
@@ -320,7 +342,7 @@ class BiometricAuthenticationPage extends StatelessWidget {
Text( Text(
"You can now either try to authenticate again or disconnect from the current server.", "You can now either try to authenticate again or disconnect from the current server.",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.bodySmall,
).padded(), ).padded(),
const SizedBox(height: 48), const SizedBox(height: 48),
Row( Row(

View File

@@ -261,7 +261,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
@override @override
Future<StoragePath?> getStoragePath(int id) { Future<StoragePath?> getStoragePath(int id) {
return getSingleResult( return getSingleResult(
"/api/storage_paths/?page=1&page_size=100000", "/api/storage_paths/$id/",
StoragePath.fromJson, StoragePath.fromJson,
ErrorCode.storagePathLoadFailed, ErrorCode.storagePathLoadFailed,
client: client, client: client,

View File

@@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.3.0+9 version: 1.3.1+10
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"