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

@@ -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,
BlocProvider.of<DocumentDetailsCubit>(context)
.state
.document),
onPressed: () => Navigator.of(context).pop(
BlocProvider.of<DocumentDetailsCubit>(context)
.state
.document,
),
),
floating: true,
pinned: true,
@@ -237,13 +235,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return ListView(
children: [
_DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context),
_separator(),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context),
_separator(),
label: S.of(context).documentAddedPropertyLabel,
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(),
label: S.of(context).documentMetaDataOriginalFileSizeLabel,
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(
text: document.content ?? "",
highlights: match == null ? [] : match.split(" "),
style: Theme.of(context).textTheme.bodyText2,
caseSensitive: false,
),
label: S.of(context).documentDetailsPageTabContentLabel,
child: HighlightedText(
text: document.content ?? "",
highlights: match == null ? [] : match.split(" "),
style: Theme.of(context).textTheme.bodyMedium,
caseSensitive: false,
),
);
).paddedOnly(top: 8);
}
Widget _buildDocumentOverview(DocumentModel document, String? match) {
@@ -314,60 +305,61 @@ 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(
content: DocumentTypeWidget(
isClickable: widget.isLabelClickable,
documentTypeId: document.documentType,
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(
).paddedSymmetrically(vertical: 16),
Visibility(
visible: document.documentType != null,
child: _DetailsItem(
content: DocumentTypeWidget(
textStyle: Theme.of(context).textTheme.bodyLarge,
isClickable: widget.isLabelClickable,
tagIds: document.tags,
isSelectedPredicate: (_) => false,
onTagSelected: (int tagId) {},
documentTypeId: document.documentType,
),
),
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(),
// 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,11 +61,12 @@ class InfoDrawer extends StatelessWidget {
).paddedOnly(right: 8.0),
Text(
S.of(context).appTitleText,
style: Theme.of(context).textTheme.headline5?.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
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,90 +127,90 @@ 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(
S.of(context).appDrawerSettingsLabel,
...[
ListTile(
title: Text(S.of(context).bottomNavInboxPageLabel),
leading: const Icon(Icons.inbox),
onTap: () => _onOpenInbox(context),
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: getIt<ApplicationSettingsCubit>(),
child: const SettingsPage(),
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(
S.of(context).appDrawerSettingsLabel,
),
),
),
const Divider(),
ListTile(
leading: const Icon(Icons.bug_report),
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),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: getIt<ApplicationSettingsCubit>(),
child: const SettingsPage(),
),
),
),
const SizedBox(height: 16),
Text(
'Credits',
style: Theme.of(context).textTheme.titleMedium,
),
_buildOnboardingImageCredits(),
],
child: Text(S.of(context).appDrawerAboutLabel),
),
const Divider(),
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);
}
},
),
const Divider(),
),
ListTile(
leading: const Icon(Icons.bug_report),
title: Text(S.of(context).appDrawerReportBugLabel),
onTap: () {
launchUrlString(
'https://github.com/astubenbord/paperless-mobile/issues/new');
},
),
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(
'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(
'${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,14 +32,15 @@ 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(
color: textColor ?? Theme.of(context).colorScheme.primary,
),
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,32 +64,61 @@ class SavedViewSelectionWidget extends StatelessWidget {
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).savedViewsLabel,
style: Theme.of(context).textTheme.titleSmall,
),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
previous.filter != current.filter,
builder: (context, docState) {
return TextButton.icon(
icon: const Icon(Icons.add),
onPressed: enabled
? () => _onCreatePressed(context, docState.filter)
: null,
label: Text(S.of(context).savedViewCreateNewLabel),
);
},
),
],
BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).savedViewsLabel,
style: Theme.of(context).textTheme.titleSmall,
),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
previous.filter != current.filter,
builder: (context, docState) {
return TextButton.icon(
icon: const Icon(Icons.add),
onPressed: (enabled && state.isLoaded)
? () => _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 {
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,
),
),
),