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
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

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/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;

View File

@@ -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: ),));
}

View File

@@ -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);

View File

@@ -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';

View File

@@ -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);

View File

@@ -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(),
]
],

View File

@@ -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},
);
}

View File

@@ -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();
// }
// },
// ),
],
),
);

View File

@@ -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,
);
}
}

View File

@@ -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);
}

View File

@@ -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 {

View File

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

View File

@@ -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.
///

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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,
),
],
),

View File

@@ -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,
),
);
},

View File

@@ -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),
),
),
),

View File

@@ -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() {

View File

@@ -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(

View File

@@ -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,

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/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';

View File

@@ -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()]),
],
),
),

View File

@@ -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(),
),

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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,

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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(

View File

@@ -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,
),
),
),

View File

@@ -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(

View File

@@ -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,

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.
# 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"