Merge branch 'main' into download_to_public

This commit is contained in:
Iulian Ciorascu
2023-02-11 20:10:34 +01:00
115 changed files with 1190 additions and 1342 deletions

View File

@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:url_launcher/link.dart';

View File

@@ -7,6 +7,8 @@ import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
part 'document_details_state.dart';
@@ -73,6 +75,24 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
emit(state.copyWith(document: document));
}
Future<void> shareDocument() async {
final documentBytes = await _api.download(state.document);
final dir = await getTemporaryDirectory();
final String path = "${dir.path}/${state.document.originalFileName}";
await File(path).writeAsBytes(documentBytes);
Share.shareXFiles(
[
XFile(
path,
name: state.document.originalFileName,
mimeType: "application/pdf",
lastModified: state.document.modified,
),
],
subject: state.document.title,
);
}
@override
Future<void> close() {
for (final element in _subscriptions) {

View File

@@ -4,31 +4,27 @@ import 'package:badges/badges.dart' as b;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_edit_page.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
//TODO: Refactor this into several widgets
class DocumentDetailsPage extends StatefulWidget {
@@ -49,7 +45,7 @@ class DocumentDetailsPage extends StatefulWidget {
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
late Future<DocumentMetaData> _metaData;
static const double _itemPadding = 24;
@override
void initState() {
super.initState();
@@ -70,7 +66,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
length: 4,
child: Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: widget.allowEdit ? _buildAppBar() : null,
floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
bottomNavigationBar: _buildBottomAppBar(),
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
@@ -96,27 +92,30 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text(
S.of(context).documentDetailsPageTabOverviewLabel,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer),
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context).documentDetailsPageTabContentLabel,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer),
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context).documentDetailsPageTabMetaDataLabel,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer),
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
@@ -146,20 +145,26 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
child: TabBarView(
children: [
_buildDocumentOverview(
state.document,
DocumentOverviewWidget(
document: state.document,
itemSpacing: _itemPadding,
queryString: widget.titleAndContentQueryString,
),
_buildDocumentContentView(
state.document,
state,
DocumentContentWidget(
isFullContentLoaded: state.isFullContentLoaded,
document: state.document,
fullContent: state.fullContent,
queryString: widget.titleAndContentQueryString,
),
_buildDocumentMetaDataView(
state.document,
DocumentMetaDataWidget(
document: state.document,
itemSpacing: _itemPadding,
metaData: _metaData,
),
const SimilarDocumentsView(),
],
),
).paddedSymmetrically(horizontal: 8);
);
},
),
),
@@ -168,7 +173,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
);
}
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState> _buildAppBar() {
Widget _buildEditButton() {
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
final _filteredSuggestions =
@@ -176,7 +181,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
if (!connectivityState.isConnected) {
return Container();
return const SizedBox.shrink();
}
return b.Badge(
position: b.BadgePosition.topEnd(top: -12, end: -6),
@@ -244,8 +249,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
IconButton(
tooltip: S.of(context).documentDetailsPageShareTooltip,
icon: const Icon(Icons.share),
onPressed:
isConnected ? () => _onShare(state.document) : null,
onPressed: isConnected
? () =>
context.read<DocumentDetailsCubit>().shareDocument()
: null,
),
],
);
@@ -265,7 +272,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: EditDocumentCubit(
value: DocumentEditCubit(
document,
documentsApi: context.read(),
correspondentRepository: context.read(),
@@ -279,7 +286,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
value: cubit,
),
],
child: BlocListener<EditDocumentCubit, EditDocumentState>(
child: BlocListener<DocumentEditCubit, DocumentEditState>(
listenWhen: (previous, current) =>
previous.document != current.document,
listener: (context, state) {
@@ -317,203 +324,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
}
Widget _buildDocumentMetaDataView(DocumentModel document) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
if (!state.isConnected) {
return const Center(
child: OfflineWidget(),
);
}
return FutureBuilder<DocumentMetaData>(
future: _metaData,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final meta = snapshot.data!;
return ListView(
children: [
_DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem(
label: S
.of(context)
.documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: TextButton.icon(
icon: const Icon(Icons.archive_outlined),
label: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed: widget.allowEdit
? () => _assignAsn(document)
: null,
),
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.mediaFilename,
context: context,
label:
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(formatBytes(meta.originalSize, 2),
label:
S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedSymmetrically(vertical: 16),
],
);
},
);
},
);
}
Future<void> _assignAsn(DocumentModel document) async {
try {
await context.read<DocumentDetailsCubit>().assignAsn(document);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
Widget _buildDocumentContentView(
DocumentModel document,
DocumentDetailsState state,
) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HighlightedText(
text: (state.isFullContentLoaded
? state.fullContent
: document.content) ??
"",
highlights: widget.titleAndContentQueryString != null
? widget.titleAndContentQueryString!.split(" ")
: [],
style: Theme.of(context).textTheme.bodyMedium,
caseSensitive: false,
),
if (!state.isFullContentLoaded && (document.content ?? '').isNotEmpty)
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
child:
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
onPressed: () {
context.read<DocumentDetailsCubit>().loadFullContent();
},
),
),
],
).padded(8).paddedOnly(top: 14),
);
}
Widget _buildDocumentOverview(DocumentModel document) {
return ListView(
children: [
_DetailsItem(
label: S.of(context).documentTitlePropertyLabel,
content: HighlightedText(
text: document.title,
highlights: widget.titleAndContentQueryString?.split(" ") ?? [],
style: Theme.of(context).textTheme.bodyLarge,
),
).paddedOnly(bottom: 16),
_DetailsItem.text(
DateFormat.yMMMMd().format(document.created),
context: context,
label: S.of(context).documentCreatedPropertyLabel,
).paddedSymmetrically(vertical: 16),
Visibility(
visible: document.documentType != null,
child: _DetailsItem(
label: S.of(context).documentDocumentTypePropertyLabel,
content: LabelText<DocumentType>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.documentType,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.correspondent != null,
child: _DetailsItem(
label: S.of(context).documentCorrespondentPropertyLabel,
content: LabelText<Correspondent>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.correspondent,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.storagePath != null,
child: _DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
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,
),
),
).paddedSymmetrically(vertical: 16),
),
],
);
}
///
/// Downloads file to temporary directory, from which it can then be shared.
///
Future<void> _onShare(DocumentModel document) async {
Uint8List documentBytes =
await context.read<PaperlessDocumentsApi>().download(document);
final dir = await getTemporaryDirectory();
final String path = "${dir.path}/${document.originalFileName}";
await File(path).writeAsBytes(documentBytes);
Share.shareXFiles(
[
XFile(
path,
name: document.originalFileName,
mimeType: "application/pdf",
lastModified: document.modified,
)
],
subject: document.title,
);
}
void _onDelete(DocumentModel document) async {
final delete = await showDialog(
context: context,
@@ -546,42 +356,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
}
class _DetailsItem extends StatelessWidget {
final String label;
final Widget content;
const _DetailsItem({
Key? key,
required this.label,
required this.content,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
content,
],
),
);
}
_DetailsItem.text(
String text, {
required this.label,
required BuildContext context,
}) : content = Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
);
}
class ColoredTabBar extends Container implements PreferredSizeWidget {
ColoredTabBar({
super.key,

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
class DetailsItem extends StatelessWidget {
final String label;
final Widget content;
const DetailsItem({
Key? key,
required this.label,
required this.content,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
content,
],
);
}
DetailsItem.text(
String text, {
required this.label,
required BuildContext context,
}) : content = Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
);
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class DocumentContentWidget extends StatelessWidget {
final bool isFullContentLoaded;
final String? fullContent;
final String? queryString;
final DocumentModel document;
const DocumentContentWidget({
super.key,
required this.isFullContentLoaded,
this.fullContent,
required this.document,
this.queryString,
});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HighlightedText(
text: (isFullContentLoaded ? fullContent : document.content) ?? "",
highlights: queryString != null ? queryString!.split(" ") : [],
style: Theme.of(context).textTheme.bodyMedium,
caseSensitive: false,
),
if (!isFullContentLoaded && (document.content ?? '').isNotEmpty)
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
child:
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
onPressed: () {
context.read<DocumentDetailsCubit>().loadFullContent();
},
),
),
],
),
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
class DocumentMetaDataWidget extends StatelessWidget {
final Future<DocumentMetaData> metaData;
final DocumentModel document;
final double itemSpacing;
const DocumentMetaDataWidget({
super.key,
required this.metaData,
required this.document,
required this.itemSpacing,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
if (!state.isConnected) {
return const Center(
child: OfflineWidget(),
);
}
return FutureBuilder<DocumentMetaData>(
future: metaData,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final meta = snapshot.data!;
return ListView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
children: [
DetailsItem(
label: S
.of(context)
.documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: TextButton.icon(
icon: const Icon(Icons.archive_outlined),
label: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed: () => _assignAsn(context),
),
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.mediaFilename,
context: context,
label:
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(formatBytes(meta.originalSize, 2),
label:
S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedOnly(bottom: itemSpacing),
],
);
},
);
},
);
}
Future<void> _assignAsn(BuildContext context) async {
try {
await context.read<DocumentDetailsCubit>().assignAsn(document);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class DocumentOverviewWidget extends StatelessWidget {
final DocumentModel document;
final String? queryString;
final double itemSpacing;
const DocumentOverviewWidget({
super.key,
required this.document,
this.queryString,
required this.itemSpacing,
});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
children: [
DetailsItem(
label: S.of(context).documentTitlePropertyLabel,
content: HighlightedText(
text: document.title,
highlights: queryString?.split(" ") ?? [],
style: Theme.of(context).textTheme.bodyLarge,
),
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(
DateFormat.yMMMMd().format(document.created),
context: context,
label: S.of(context).documentCreatedPropertyLabel,
).paddedOnly(bottom: itemSpacing),
Visibility(
visible: document.documentType != null,
child: DetailsItem(
label: S.of(context).documentDocumentTypePropertyLabel,
content: LabelText<DocumentType>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.documentType,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.correspondent != null,
child: DetailsItem(
label: S.of(context).documentCorrespondentPropertyLabel,
content: LabelText<Correspondent>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.correspondent,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.storagePath != null,
child: DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
pathId: document.storagePath,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.tags.isNotEmpty,
child: DetailsItem(
label: S.of(context).documentTagsPropertyLabel,
content: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget(
isClickable: false,
tagIds: document.tags,
),
),
).paddedOnly(bottom: itemSpacing),
),
],
);
}
}

View File

@@ -1,19 +1,15 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:collection/collection.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
part 'edit_document_state.dart';
part 'document_edit_state.dart';
class EditDocumentCubit extends Cubit<EditDocumentState> {
class DocumentEditCubit extends Cubit<DocumentEditState> {
final DocumentModel _initialDocument;
final PaperlessDocumentsApi _docsApi;
@@ -24,7 +20,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
final LabelRepository<Tag> _tagRepository;
final List<StreamSubscription> _subscriptions = [];
EditDocumentCubit(
DocumentEditCubit(
DocumentModel document, {
required PaperlessDocumentsApi documentsApi,
required LabelRepository<Correspondent> correspondentRepository,
@@ -40,7 +36,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
_tagRepository = tagRepository,
_notifier = notifier,
super(
EditDocumentState(
DocumentEditState(
document: document,
correspondents: correspondentRepository.current?.values ?? {},
documentTypes: documentTypeRepository.current?.values ?? {},

View File

@@ -1,6 +1,6 @@
part of 'edit_document_cubit.dart';
part of 'document_edit_cubit.dart';
class EditDocumentState extends Equatable {
class DocumentEditState extends Equatable {
final DocumentModel document;
final Map<int, Correspondent> correspondents;
@@ -8,7 +8,7 @@ class EditDocumentState extends Equatable {
final Map<int, StoragePath> storagePaths;
final Map<int, Tag> tags;
const EditDocumentState({
const DocumentEditState({
required this.correspondents,
required this.documentTypes,
required this.storagePaths,
@@ -25,14 +25,14 @@ class EditDocumentState extends Equatable {
document,
];
EditDocumentState copyWith({
DocumentEditState copyWith({
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, StoragePath>? storagePaths,
Map<int, Tag>? tags,
DocumentModel? document,
}) {
return EditDocumentState(
return DocumentEditState(
document: document ?? this.document,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,

View File

@@ -13,7 +13,7 @@ import 'package:paperless_mobile/core/repository/state/impl/document_type_reposi
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.dart';
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
@@ -51,12 +51,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
void initState() {
super.initState();
_filteredSuggestions = widget.suggestions
.documentDifference(context.read<EditDocumentCubit>().state.document);
.documentDifference(context.read<DocumentEditCubit>().state.document);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<EditDocumentCubit, EditDocumentState>(
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
builder: (context, state) {
return Scaffold(
resizeToAvoidBottomInset: false,
@@ -252,7 +252,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
_isSubmitLoading = true;
});
try {
await context.read<EditDocumentCubit>().updateDocument(mergedDocument);
await context.read<DocumentEditCubit>().updateDocument(mergedDocument);
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);

View File

@@ -19,8 +19,8 @@ import 'package:paperless_mobile/features/document_search/view/document_search_p
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/scan/view/widgets/scanned_image_item.dart';
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart';
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -2,11 +2,17 @@ import 'package:collection/collection.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'document_search_state.dart';
part 'document_search_cubit.g.dart';
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
with PagedDocumentsMixin {
with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
@override

View File

@@ -1,9 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
part 'document_search_state.g.dart';
part of 'document_search_cubit.dart';
enum SearchView {
suggestions,
@@ -11,7 +6,7 @@ enum SearchView {
}
@JsonSerializable(ignoreUnannotated: true)
class DocumentSearchState extends PagedDocumentsState {
class DocumentSearchState extends DocumentPagingState {
@JsonKey()
final List<String> searchHistory;
final SearchView view;

View File

@@ -1,10 +1,8 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';

View File

@@ -5,11 +5,16 @@ import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'documents_state.dart';
part 'documents_cubit.g.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState>
with PagedDocumentsMixin {
with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
@@ -94,4 +99,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
notifier.unsubscribe(this);
return super.close();
}
void setViewType(ViewType viewType) {
emit(state.copyWith(viewType: viewType));
}
}

View File

@@ -1,13 +1,15 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
part of 'documents_cubit.dart';
class DocumentsState extends PagedDocumentsState {
@JsonKey(includeFromJson: true, includeToJson: false)
@JsonSerializable()
class DocumentsState extends DocumentPagingState {
@JsonKey(includeFromJson: false, includeToJson: false)
final List<DocumentModel> selection;
final ViewType viewType;
const DocumentsState({
this.selection = const [],
this.viewType = ViewType.list,
super.value = const [],
super.filter = const DocumentFilter(),
super.hasLoaded = false,
@@ -22,6 +24,7 @@ class DocumentsState extends PagedDocumentsState {
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
List<DocumentModel>? selection,
ViewType? viewType,
}) {
return DocumentsState(
hasLoaded: hasLoaded ?? this.hasLoaded,
@@ -29,38 +32,22 @@ class DocumentsState extends PagedDocumentsState {
value: value ?? this.value,
filter: filter ?? this.filter,
selection: selection ?? this.selection,
viewType: viewType ?? this.viewType,
);
}
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
_$DocumentsStateFromJson(json);
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
@override
List<Object?> get props => [
selection,
viewType,
...super.props,
];
Map<String, dynamic> toJson() {
final json = {
'hasLoaded': hasLoaded,
'isLoading': isLoading,
'filter': filter.toJson(),
'value':
value.map((e) => e.toJson(DocumentModelJsonConverter())).toList(),
};
return json;
}
factory DocumentsState.fromJson(Map<String, dynamic> json) {
return DocumentsState(
hasLoaded: json['hasLoaded'],
isLoading: json['isLoading'],
value: (json['value'] as List<dynamic>)
.map((e) =>
PagedSearchResult.fromJsonT(e, DocumentModelJsonConverter()))
.toList(),
filter: DocumentFilter.fromJson(json['filter']),
);
}
@override
DocumentsState copyWithPaged({
bool? hasLoaded,

View File

@@ -1,7 +1,4 @@
import 'dart:developer';
import 'package:badges/badges.dart' as b;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
@@ -9,21 +6,18 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/labels_bloc_provider.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
@@ -263,14 +257,6 @@ class _DocumentsPageState extends State<DocumentsPage>
),
_buildViewActions(),
BlocBuilder<DocumentsCubit, DocumentsState>(
// Not required anymore since saved views are now handled separately
// buildWhen: (previous, current) =>
// !const ListEquality().equals(
// previous.documents,
// current.documents,
// ) ||
// previous.selectedIds !=
// current.selectedIds,
builder: (context, state) {
if (state.hasLoaded &&
state.documents.isEmpty) {
@@ -285,34 +271,27 @@ class _DocumentsPageState extends State<DocumentsPage>
),
);
}
return BlocBuilder<
ApplicationSettingsCubit,
ApplicationSettingsState>(
builder: (context, settings) {
return SliverAdaptiveDocumentsView(
viewType:
settings.preferredViewType,
onTap: _openDetails,
onSelected: context
.read<DocumentsCubit>()
.toggleDocumentSelection,
hasInternetConnection:
connectivityState.isConnected,
onTagSelected: _addTagToFilter,
onCorrespondentSelected:
_addCorrespondentToFilter,
onDocumentTypeSelected:
_addDocumentTypeToFilter,
onStoragePathSelected:
_addStoragePathToFilter,
documents: state.documents,
hasLoaded: state.hasLoaded,
isLabelClickable: true,
isLoading: state.isLoading,
selectedDocumentIds:
state.selectedIds,
);
},
return SliverAdaptiveDocumentsView(
viewType: state.viewType,
onTap: _openDetails,
onSelected: context
.read<DocumentsCubit>()
.toggleDocumentSelection,
hasInternetConnection:
connectivityState.isConnected,
onTagSelected: _addTagToFilter,
onCorrespondentSelected:
_addCorrespondentToFilter,
onDocumentTypeSelected:
_addDocumentTypeToFilter,
onStoragePathSelected:
_addStoragePathToFilter,
documents: state.documents,
hasLoaded: state.hasLoaded,
isLabelClickable: true,
isLoading: state.isLoading,
selectedDocumentIds: state.selectedIds,
);
},
),
@@ -360,18 +339,11 @@ class _DocumentsPageState extends State<DocumentsPage>
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SortDocumentsButton(),
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) {
return IconButton(
icon: Icon(
state.preferredViewType == ViewType.list
? Icons.grid_view_rounded
: Icons.list,
),
onPressed: () =>
context.read<ApplicationSettingsCubit>().setViewType(
state.preferredViewType.toggle(),
),
return ViewTypeSelectionWidget(
viewType: state.viewType,
onChanged: context.read<DocumentsCubit>().setViewType,
);
},
)

View File

@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/empty_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class DocumentsEmptyState extends StatelessWidget {
final PagedDocumentsState state;
final DocumentPagingState state;
final VoidCallback? onReset;
const DocumentsEmptyState({
Key? key,

View File

@@ -4,9 +4,8 @@ import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';

View File

@@ -3,9 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -1,18 +1,10 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/text_query_form_field.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart';
enum DateRangeSelection { before, after }

View File

@@ -4,8 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class SortFieldSelectionBottomSheet extends StatefulWidget {

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_type_ahead.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:provider/provider.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class BulkDeleteConfirmationDialog extends StatelessWidget {

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
/// Meant to be used with blocbuilder.
class ViewTypeSelectionWidget extends StatelessWidget {
final ViewType viewType;
final void Function(ViewType type) onChanged;
const ViewTypeSelectionWidget({
super.key,
required this.viewType,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final next = viewType.toggle();
final icon = next == ViewType.grid ? Icons.grid_view_rounded : Icons.list;
return IconButton(
icon: Icon(icon),
onPressed: () {
onChanged(next);
},
);
}
}

View File

@@ -2,13 +2,10 @@ 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/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class SortDocumentsButton extends StatelessWidget {
const SortDocumentsButton({

View File

@@ -1,39 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
class ViewActions extends StatelessWidget {
const ViewActions({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SortDocumentsButton(),
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
builder: (context, settings) {
final cubit = context.read<ApplicationSettingsCubit>();
switch (settings.preferredViewType) {
case ViewType.grid:
return IconButton(
icon: const Icon(Icons.list),
onPressed: () =>
cubit.setViewType(settings.preferredViewType.toggle()),
);
case ViewType.list:
return IconButton(
icon: const Icon(Icons.grid_view_rounded),
onPressed: () =>
cubit.setViewType(settings.preferredViewType.toggle()),
);
}
},
)
],
);
}
}

View File

@@ -1,10 +1,11 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart';
part 'edit_label_state.dart';
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
final LabelRepository<T> _repository;
@@ -13,7 +14,7 @@ class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
EditLabelCubit(LabelRepository<T> repository)
: _repository = repository,
super(const EditLabelInitial()) {
super(EditLabelState<T>(labels: repository.current?.values ?? {})) {
_subscription = repository.values.listen(
(event) => emit(EditLabelState(labels: event?.values ?? {})),
);

View File

@@ -1,16 +1,10 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
part of 'edit_label_cubit.dart';
@immutable
class EditLabelState<T> extends Equatable {
final Map<int, T> labels;
const EditLabelState({required this.labels});
const EditLabelState({this.labels = const {}});
@override
List<Object> get props => [labels];
}
class EditLabelInitial<T> extends EditLabelState<T> {
const EditLabelInitial() : super(labels: const {});
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
@@ -10,25 +12,21 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/document_scan/view/scanner_page.dart';
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/features/home/view/route_description.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -45,14 +43,16 @@ class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
int _currentIndex = 0;
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
late final InboxCubit _inboxCubit;
late Timer _inboxTimer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initializeData(context);
_inboxCubit = InboxCubit(
context.read(),
@@ -62,12 +62,43 @@ class _HomePageState extends State<HomePage> {
context.read(),
context.read(),
);
_listenToInboxChanges();
context.read<ConnectivityCubit>().reload();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_listenForReceivedFiles();
});
}
void _listenToInboxChanges() {
_inboxCubit.refreshItemsInInboxCount();
_inboxTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
if (!mounted) {
timer.cancel();
} else {
_inboxCubit.refreshItemsInInboxCount();
}
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed && !_inboxTimer.isActive) {
log('App is now in foreground, start polling for statistics.');
_listenToInboxChanges();
} else if (state != AppLifecycleState.resumed) {
log('App is now in background, stop polling for statistics.');
_inboxTimer.cancel();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_inboxTimer.cancel();
_inboxCubit.close();
super.dispose();
}
void _listenForReceivedFiles() async {
if (ShareIntentQueue.instance.hasUnhandledFiles) {
await _handleReceivedFile(ShareIntentQueue.instance.pop()!);
@@ -156,12 +187,6 @@ class _HomePageState extends State<HomePage> {
}
}
@override
void dispose() {
_inboxCubit.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
final destinations = [
@@ -247,8 +272,18 @@ class _HomePageState extends State<HomePage> {
],
child: const LabelsPage(),
),
BlocProvider.value(
value: _inboxCubit,
MultiBlocProvider(
providers: [
// We need to manually downcast the inboxcubit to the
// mixed-in DocumentPagingBlocMixin to use the
// DocumentPagingViewMixin in the inbox.
BlocProvider<DocumentPagingBlocMixin>.value(
value: _inboxCubit,
),
BlocProvider<InboxCubit>.value(
value: _inboxCubit,
),
],
child: const InboxPage(),
),
// const SettingsPage(),

View File

@@ -13,7 +13,7 @@
// import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
// import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
// import 'package:paperless_mobile/extensions/flutter_extensions.dart';
// import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
// import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
// import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
// import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';

View File

@@ -3,13 +3,9 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:provider/provider.dart';

View File

@@ -1,14 +1,18 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
part 'inbox_cubit.g.dart';
part 'inbox_state.dart';
class InboxCubit extends HydratedCubit<InboxState>
with DocumentPagingBlocMixin {
final LabelRepository<Tag> _tagsRepository;
final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
@@ -82,13 +86,6 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
refreshItemsInInboxCount(false);
loadInbox();
Timer.periodic(const Duration(seconds: 5), (timer) {
if (isClosed) {
timer.cancel();
}
refreshItemsInInboxCount();
});
}
void refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {

View File

@@ -1,11 +1,7 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
part 'inbox_state.g.dart';
part of 'inbox_cubit.dart';
@JsonSerializable(ignoreUnannotated: true)
class InboxState extends PagedDocumentsState {
class InboxState extends DocumentPagingState {
final Iterable<int> inboxTags;
final Map<int, Tag> availableTags;

View File

@@ -9,10 +9,10 @@ import 'package:paperless_mobile/features/documents/view/widgets/documents_list_
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/extensions/dart_extensions.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
@@ -24,35 +24,15 @@ class InboxPage extends StatefulWidget {
State<InboxPage> createState() => _InboxPageState();
}
class _InboxPageState extends State<InboxPage> {
final ScrollController _scrollController = ScrollController();
class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin {
@override
final pagingScrollController = ScrollController();
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
@override
void initState() {
super.initState();
context.read<InboxCubit>().loadInbox();
_scrollController.addListener(_listenForLoadNewData);
}
@override
void dispose() {
_scrollController.removeListener(_listenForLoadNewData);
super.dispose();
}
void _listenForLoadNewData() {
final currState = context.read<InboxCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
context.read<InboxCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override
@@ -144,7 +124,7 @@ class _InboxPageState extends State<InboxPage> {
physics: state.documents.isEmpty
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
controller: _scrollController,
controller: pagingScrollController,
slivers: [
SearchAppBar(
hintText: S.of(context).documentSearchSearchDocuments,

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class InboxEmptyWidget extends StatelessWidget {

View File

@@ -5,7 +5,7 @@ import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -1,10 +1,8 @@
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/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/correspondent_bloc_provider.dart';
class CorrespondentWidget extends StatelessWidget {
final int? correspondentId;

View File

@@ -3,7 +3,8 @@ import 'dart:async';
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/labels/bloc/label_state.dart';
part 'label_state.dart';
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
final LabelRepository<T> _repository;

View File

@@ -1,4 +1,4 @@
import 'package:paperless_api/paperless_api.dart';
part of 'label_cubit.dart';
class LabelState<T extends Label> {
LabelState.initial() : this(isLoaded: false, labels: {});

View File

@@ -3,7 +3,7 @@ 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/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class CorrespondentBlocProvider extends StatelessWidget {
final Widget child;

View File

@@ -3,7 +3,7 @@ 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/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class DocumentTypeBlocProvider extends StatelessWidget {
final Widget child;

View File

@@ -6,7 +6,7 @@ import 'package:paperless_mobile/core/repository/state/impl/correspondent_reposi
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class LabelsBlocProvider extends StatelessWidget {
final Widget child;

View File

@@ -3,7 +3,7 @@ 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/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class StoragePathBlocProvider extends StatelessWidget {
final Widget child;

View File

@@ -3,7 +3,7 @@ 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/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class TagBlocProvider extends StatelessWidget {
final Widget child;

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
class DocumentTypeWidget extends StatelessWidget {
final int? documentTypeId;

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/storage_path_bloc_provider.dart';
class StoragePathWidget extends StatelessWidget {
final int? pathId;

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/tag_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
class TagsWidget extends StatelessWidget {

View File

@@ -15,7 +15,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_corresponden
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/pages/linked_documents_page.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';
class LabelItem<T extends Label> extends StatelessWidget {

View File

@@ -3,9 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';

View File

@@ -2,8 +2,7 @@ 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/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class LabelText<T extends Label> extends StatelessWidget {
final int? id;

View File

@@ -1,37 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState>
with PagedDocumentsMixin {
@override
final PaperlessDocumentsApi api;
@override
final DocumentChangedNotifier notifier;
LinkedDocumentsCubit(
DocumentFilter filter,
this.api,
this.notifier,
) : super(const LinkedDocumentsState()) {
updateFilter(filter: filter);
notifier.subscribe(
this,
onUpdated: replace,
onDeleted: remove,
);
}
@override
Future<void> update(DocumentModel document) async {
final updated = await api.update(document);
if (!state.filter.matches(updated)) {
remove(document);
} else {
replace(document);
}
}
}

View File

@@ -0,0 +1,56 @@
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'linked_documents_state.dart';
part 'linked_documents_cubit.g.dart';
class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
@override
final DocumentChangedNotifier notifier;
LinkedDocumentsCubit(
DocumentFilter filter,
this.api,
this.notifier,
) : super(const LinkedDocumentsState()) {
updateFilter(filter: filter);
notifier.subscribe(
this,
onUpdated: replace,
onDeleted: remove,
);
}
@override
Future<void> update(DocumentModel document) async {
final updated = await api.update(document);
if (!state.filter.matches(updated)) {
remove(document);
} else {
replace(document);
}
}
void setViewType(ViewType type) {
emit(state.copyWith(viewType: type));
}
@override
LinkedDocumentsState? fromJson(Map<String, dynamic> json) {
return LinkedDocumentsState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(LinkedDocumentsState state) {
return state.toJson();
}
}

View File

@@ -1,8 +1,11 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
part of 'linked_documents_cubit.dart';
class LinkedDocumentsState extends PagedDocumentsState {
@JsonSerializable(ignoreUnannotated: true)
class LinkedDocumentsState extends DocumentPagingState {
@JsonKey()
final ViewType viewType;
const LinkedDocumentsState({
this.viewType = ViewType.list,
super.filter,
super.isLoading,
super.hasLoaded,
@@ -14,12 +17,14 @@ class LinkedDocumentsState extends PagedDocumentsState {
bool? isLoading,
bool? hasLoaded,
List<PagedSearchResult<DocumentModel>>? value,
ViewType? viewType,
}) {
return LinkedDocumentsState(
filter: filter ?? this.filter,
isLoading: isLoading ?? this.isLoading,
hasLoaded: hasLoaded ?? this.hasLoaded,
value: value ?? this.value,
viewType: viewType ?? this.viewType,
);
}
@@ -40,9 +45,12 @@ class LinkedDocumentsState extends PagedDocumentsState {
@override
List<Object?> get props => [
filter,
isLoading,
hasLoaded,
value,
viewType,
...super.props,
];
factory LinkedDocumentsState.fromJson(Map<String, dynamic> json) =>
_$LinkedDocumentsStateFromJson(json);
Map<String, dynamic> toJson() => _$LinkedDocumentsStateToJson(this);
}

View File

@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';
class LinkedDocumentsPage extends StatefulWidget {
const LinkedDocumentsPage({super.key});
@override
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
}
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
with DocumentPagingViewMixin {
@override
final pagingScrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).linkedDocumentsPageTitle),
actions: [
BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
builder: (context, state) {
return ViewTypeSelectionWidget(
viewType: state.viewType,
onChanged: context.read<LinkedDocumentsCubit>().setViewType,
);
},
),
],
),
body: BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
builder: (context, state) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) {
return CustomScrollView(
controller: pagingScrollController,
slivers: [
SliverAdaptiveDocumentsView(
viewType: state.viewType,
documents: state.documents,
hasInternetConnection: connectivity.isConnected,
isLabelClickable: false,
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: (document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
},
),
],
);
},
);
},
),
);
}
}

View File

@@ -1,76 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';
class LinkedDocumentsPage extends StatefulWidget {
const LinkedDocumentsPage({super.key});
@override
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
}
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_listenForLoadNewData);
}
void _listenForLoadNewData() async {
final currState = context.read<LinkedDocumentsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<LinkedDocumentsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).linkedDocumentsPageTitle),
),
body: BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
builder: (context, state) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) {
return DefaultAdaptiveDocumentsView(
scrollController: _scrollController,
documents: state.documents,
hasInternetConnection: connectivity.isConnected,
isLabelClickable: false,
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: (document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
},
);
},
);
},
),
);
}
}

View File

@@ -1,12 +1,14 @@
import 'package:dio/dio.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/security/session_manager.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/user_credentials.model.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
part 'authentication_state.dart';
part 'authentication_cubit.g.dart';
class AuthenticationCubit extends Cubit<AuthenticationState>
with HydratedMixin<AuthenticationState> {

View File

@@ -1,12 +1,9 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
part 'authentication_state.g.dart';
part of 'authentication_cubit.dart';
@JsonSerializable()
class AuthenticationState {
final bool wasLoginStored;
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
final bool? wasLocalAuthenticationSuccessful;
final AuthenticationInformation? authentication;

View File

@@ -3,17 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/constants.dart';
import 'widgets/never_scrollable_scroll_behavior.dart';
import 'widgets/login_pages/server_login_page.dart';
import 'widgets/never_scrollable_scroll_behavior.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);

View File

@@ -63,25 +63,48 @@ class _ClientCertificateFormFieldState
),
child: Column(
children: [
ListTile(
leading: ElevatedButton(
onPressed: () => _onSelectFile(field),
child: Text(S.of(context).genericActionSelectText),
),
title: _buildSelectedFileText(field),
trailing: AbsorbPointer(
absorbing: field.value == null,
child: _selectedFile != null
? IconButton(
icon: const Icon(Icons.close),
onPressed: () => setState(() {
_selectedFile = null;
field.didChange(null);
}),
)
: null,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
ElevatedButton(
onPressed: () => _onSelectFile(field),
child:
Text(S.of(context).genericActionSelectText),
),
_buildSelectedFileText(field).paddedOnly(left: 8),
],
),
if (_selectedFile != null)
IconButton(
icon: const Icon(Icons.close),
onPressed: () => setState(() {
_selectedFile = null;
field.didChange(null);
}),
)
],
).padded(8),
// ListTile(
// leading: ElevatedButton(
// onPressed: () => _onSelectFile(field),
// child: Text(S.of(context).genericActionSelectText),
// ),
// title: _buildSelectedFileText(field),
// trailing: AbsorbPointer(
// absorbing: field.value == null,
// child: _selectedFile != null
// ? IconButton(
// icon: const Icon(Icons.close),
// onPressed: () => setState(() {
// _selectedFile = null;
// field.didChange(null);
// }),
// )
// : null,
// ),
// ),
if (_selectedFile != null) ...[
ObscuredInputTextFormField(
key: const ValueKey('login-client-cert-passphrase'),
@@ -127,7 +150,9 @@ class _ClientCertificateFormFieldState
assert(_selectedFile == null);
return Text(
S.of(context).loginPageClientCertificateSettingSelectFileText,
style: TextStyle(color: Theme.of(context).hintColor),
style: Theme.of(context).textTheme.labelMedium?.apply(
color: Theme.of(context).hintColor,
),
);
} else {
assert(_selectedFile != null);

View File

@@ -1,17 +1,15 @@
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'model/paged_documents_state.dart';
import 'paged_documents_state.dart';
///
/// Mixin which can be used on cubits that handle documents.
/// This implements all paging and filtering logic.
///
mixin PagedDocumentsMixin<State extends PagedDocumentsState>
mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
on BlocBase<State> {
PaperlessDocumentsApi get api;
DocumentChangedNotifier get notifier;

View File

@@ -5,13 +5,13 @@ import 'package:paperless_api/paperless_api.dart';
/// Base state for all blocs/cubits using a paged view of documents.
/// [T] is the return type of the API call.
///
abstract class PagedDocumentsState extends Equatable {
abstract class DocumentPagingState extends Equatable {
final bool hasLoaded;
final bool isLoading;
final List<PagedSearchResult<DocumentModel>> value;
final DocumentFilter filter;
const PagedDocumentsState({
const DocumentPagingState({
this.value = const [],
this.hasLoaded = false,
this.isLoading = false,

View File

@@ -0,0 +1,43 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
mixin DocumentPagingViewMixin<T extends StatefulWidget> on State<T> {
ScrollController get pagingScrollController;
@override
void initState() {
super.initState();
pagingScrollController.addListener(shouldLoadMoreDocumentsListener);
}
@override
void dispose() {
pagingScrollController.removeListener(shouldLoadMoreDocumentsListener);
super.dispose();
}
DocumentPagingBlocMixin get _bloc => context.read<DocumentPagingBlocMixin>();
void shouldLoadMoreDocumentsListener() async {
if (shouldLoadMoreDocuments) {
try {
await _bloc.loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
bool get shouldLoadMoreDocuments {
final currState = _bloc.state;
return pagingScrollController.position.maxScrollExtent != 0 &&
!currState.isLoading &&
!currState.isLastPageLoaded &&
pagingScrollController.offset >=
pagingScrollController.position.maxScrollExtent * 0.75;
}
}

View File

@@ -1,9 +1,11 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.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';
part 'saved_view_state.dart';
class SavedViewCubit extends Cubit<SavedViewState> {
final SavedViewRepository _repository;

View File

@@ -1,30 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
part 'saved_view_details_state.dart';
class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
with PagedDocumentsMixin {
@override
final PaperlessDocumentsApi api;
@override
final DocumentChangedNotifier notifier;
final SavedView savedView;
SavedViewDetailsCubit(
this.api,
this.notifier, {
required this.savedView,
}) : super(const SavedViewDetailsState()) {
notifier.subscribe(
this,
onDeleted: remove,
onUpdated: replace,
);
updateFilter(filter: savedView.toDocumentFilter());
}
}

View File

@@ -1,7 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
part of 'saved_view_cubit.dart';
class SavedViewState with EquatableMixin {
class SavedViewState extends Equatable {
final bool hasLoaded;
final Map<int, SavedView> value;

View File

@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
class AddSavedViewPage extends StatefulWidget {
final DocumentFilter currentFilter;

View File

@@ -1,12 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
import 'package:paperless_mobile/features/saved_view/view/saved_view_page.dart';
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class SavedViewList extends StatelessWidget {
@@ -48,7 +45,7 @@ class SavedViewList extends StatelessWidget {
),
),
],
child: SavedViewPage(
child: SavedViewDetailsPage(
onDelete: savedViewCubit.remove,
),
),

View File

@@ -1,130 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/view_actions.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';
class SavedViewPage extends StatefulWidget {
final Future<void> Function(SavedView savedView) onDelete;
const SavedViewPage({
super.key,
required this.onDelete,
});
@override
State<SavedViewPage> createState() => _SavedViewPageState();
}
class _SavedViewPageState extends State<SavedViewPage> {
final _scrollController = ScrollController();
ViewType _viewType = ViewType.list;
SavedView get _savedView => context.read<SavedViewDetailsCubit>().savedView;
@override
void initState() {
super.initState();
_scrollController.addListener(_listenForLoadNewData);
}
void _listenForLoadNewData() async {
final currState = context.read<SavedViewDetailsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.7 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<SavedViewDetailsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
builder: (context, state) {
return Text(_savedView.name);
},
),
actions: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
final shouldDelete = await showDialog<bool>(
context: context,
builder: (context) =>
ConfirmDeleteSavedViewDialog(view: _savedView),
) ??
false;
if (shouldDelete) {
await widget.onDelete(_savedView);
Navigator.pop(context);
}
},
),
IconButton(
icon: Icon(
_viewType == ViewType.list ? Icons.grid_view_rounded : Icons.list,
),
onPressed: () => setState(() => _viewType = _viewType.toggle()),
),
],
),
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
builder: (context, state) {
if (state.hasLoaded && state.documents.isEmpty) {
return DocumentsEmptyState(state: state);
}
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) {
return CustomScrollView(
controller: _scrollController,
slivers: [
SliverAdaptiveDocumentsView(
documents: state.documents,
hasInternetConnection: connectivity.isConnected,
isLabelClickable: false,
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: _onOpenDocumentDetails,
viewType: _viewType,
),
if (state.hasLoaded && state.isLoading)
const SliverToBoxAdapter(
child: Center(
child: CircularProgressIndicator(),
),
)
],
);
},
);
},
),
);
}
void _onOpenDocumentDetails(DocumentModel document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
}
}

View File

@@ -1,218 +0,0 @@
// import 'dart:math';
// import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:paperless_api/paperless_api.dart';
// import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
// import 'package:paperless_mobile/extensions/flutter_extensions.dart';
// import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
// import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
// import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
// import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
// 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/helpers/message_helpers.dart';
// import 'package:paperless_mobile/constants.dart';
// import 'package:shimmer/shimmer.dart';
// class SavedViewSelectionWidget extends StatelessWidget {
// final DocumentFilter currentFilter;
// const SavedViewSelectionWidget({
// Key? key,
// required this.height,
// required this.enabled,
// required this.currentFilter,
// }) : super(key: key);
// final double height;
// final bool enabled;
// @override
// Widget build(BuildContext context) {
// return BlocBuilder<ConnectivityCubit, ConnectivityState>(
// builder: (context, connectivityState) {
// final hasInternetConnection = connectivityState.isConnected;
// return SizedBox(
// height: height,
// child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
// children: [
// BlocBuilder<SavedViewCubit, SavedViewState>(
// builder: (context, state) {
// if (!state.hasLoaded) {
// return _buildLoadingWidget(context);
// }
// if (state.value.isEmpty) {
// return Text(S.of(context).savedViewsEmptyStateText);
// }
// return SizedBox(
// height: 38,
// child: ListView.separated(
// itemCount: state.value.length,
// scrollDirection: Axis.horizontal,
// itemBuilder: (context, index) {
// final view = state.value.values.elementAt(index);
// return GestureDetector(
// onLongPress: hasInternetConnection
// ? () => _onDelete(context, view)
// : null,
// child: BlocBuilder<DocumentsCubit, DocumentsState>(
// builder: (context, docState) {
// final view = state.value.values.toList()[index];
// return FilterChip(
// label: Text(
// view.name,
// ),
// selected:
// view.id == docState.selectedSavedViewId,
// onSelected: enabled && hasInternetConnection
// ? (isSelected) =>
// _onSelected(isSelected, context, view)
// : null,
// );
// },
// ),
// );
// },
// separatorBuilder: (context, index) => const SizedBox(
// width: 4.0,
// ),
// ),
// );
// },
// ),
// 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.hasLoaded &&
// hasInternetConnection)
// ? () =>
// _onCreatePressed(context, docState.filter)
// : null,
// label: Text(S.of(context).savedViewCreateNewLabel),
// );
// },
// ),
// ],
// );
// },
// ),
// ],
// ).padded(),
// );
// },
// );
// }
// Widget _buildLoadingWidget(BuildContext context) {
// return SizedBox(
// height: 38,
// width: MediaQuery.of(context).size.width,
// 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(
// scrollDirection: Axis.horizontal,
// physics: const NeverScrollableScrollPhysics(),
// children: [
// FilterChip(
// label: const SizedBox(width: 32),
// onSelected: (_) {},
// ),
// const SizedBox(width: 4.0),
// FilterChip(
// label: const SizedBox(width: 64),
// onSelected: (_) {},
// ),
// const SizedBox(width: 4.0),
// FilterChip(
// label: const SizedBox(width: 100),
// onSelected: (_) {},
// ),
// const SizedBox(width: 4.0),
// FilterChip(
// label: const SizedBox(width: 32),
// onSelected: (_) {},
// ),
// const SizedBox(width: 4.0),
// FilterChip(
// label: const SizedBox(width: 48),
// onSelected: (_) {},
// ),
// ],
// ),
// ),
// );
// }
// void _onCreatePressed(BuildContext context, DocumentFilter filter) async {
// final newView = await Navigator.of(context).push<SavedView?>(
// MaterialPageRoute(
// builder: (context) => AddSavedViewPage(
// currentFilter: filter,
// ),
// ),
// );
// if (newView != null) {
// try {
// await context.read<SavedViewCubit>().add(newView);
// } on PaperlessServerException catch (error, stackTrace) {
// showErrorMessage(context, error, stackTrace);
// }
// }
// }
// void _onSelected(
// bool selectionIntent,
// BuildContext context,
// SavedView view,
// ) async {
// if (selectionIntent) {
// context.read<DocumentsCubit>().selectView(view.id!);
// } else {
// context.read<DocumentsCubit>().unselectView();
// context.read<DocumentsCubit>().resetFilter();
// }
// }
// void _onDelete(BuildContext context, SavedView view) async {
// {
// final delete = await showDialog<bool>(
// context: context,
// builder: (context) => ConfirmDeleteSavedViewDialog(view: view),
// ) ??
// false;
// if (delete) {
// try {
// context.read<SavedViewCubit>().remove(view);
// if (context.read<DocumentsCubit>().state.selectedSavedViewId ==
// view.id) {
// await context.read<DocumentsCubit>().resetFilter();
// }
// } on PaperlessServerException catch (error, stackTrace) {
// showErrorMessage(context, error, stackTrace);
// }
// }
// }
// }
// }

View File

@@ -0,0 +1,47 @@
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'saved_view_details_cubit.g.dart';
part 'saved_view_details_state.dart';
class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
@override
final DocumentChangedNotifier notifier;
final SavedView savedView;
SavedViewDetailsCubit(
this.api,
this.notifier, {
required this.savedView,
}) : super(const SavedViewDetailsState()) {
notifier.subscribe(
this,
onDeleted: remove,
onUpdated: replace,
);
updateFilter(filter: savedView.toDocumentFilter());
}
void setViewType(ViewType viewType) {
emit(state.copyWith(viewType: viewType));
}
@override
SavedViewDetailsState? fromJson(Map<String, dynamic> json) {
return SavedViewDetailsState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(SavedViewDetailsState state) {
return state.toJson();
}
}

View File

@@ -1,7 +1,12 @@
part of 'saved_view_details_cubit.dart';
class SavedViewDetailsState extends PagedDocumentsState {
@JsonSerializable(ignoreUnannotated: true)
class SavedViewDetailsState extends DocumentPagingState {
@JsonKey()
final ViewType viewType;
const SavedViewDetailsState({
this.viewType = ViewType.list,
super.filter,
super.hasLoaded,
super.isLoading,
@@ -10,10 +15,8 @@ class SavedViewDetailsState extends PagedDocumentsState {
@override
List<Object?> get props => [
filter,
hasLoaded,
isLoading,
value,
viewType,
...super.props,
];
@override
@@ -36,12 +39,19 @@ class SavedViewDetailsState extends PagedDocumentsState {
bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
ViewType? viewType,
}) {
return SavedViewDetailsState(
hasLoaded: hasLoaded ?? this.hasLoaded,
isLoading: isLoading ?? this.isLoading,
value: value ?? this.value,
filter: filter ?? this.filter,
viewType: viewType ?? this.viewType,
);
}
factory SavedViewDetailsState.fromJson(Map<String, dynamic> json) =>
_$SavedViewDetailsStateFromJson(json);
Map<String, dynamic> toJson() => _$SavedViewDetailsStateToJson(this);
}

View File

@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';
class SavedViewDetailsPage extends StatefulWidget {
final Future<void> Function(SavedView savedView) onDelete;
const SavedViewDetailsPage({
super.key,
required this.onDelete,
});
@override
State<SavedViewDetailsPage> createState() => _SavedViewDetailsPageState();
}
class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
with DocumentPagingViewMixin {
@override
final pagingScrollController = ScrollController();
@override
Widget build(BuildContext context) {
final cubit = context.read<SavedViewDetailsCubit>();
return Scaffold(
appBar: AppBar(
title: Text(cubit.savedView.name),
actions: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
final shouldDelete = await showDialog<bool>(
context: context,
builder: (context) => ConfirmDeleteSavedViewDialog(
view: cubit.savedView,
),
) ??
false;
if (shouldDelete) {
await widget.onDelete(cubit.savedView);
Navigator.pop(context);
}
},
),
BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
builder: (context, state) {
return ViewTypeSelectionWidget(
viewType: state.viewType,
onChanged: cubit.setViewType,
);
},
)
],
),
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
builder: (context, state) {
if (state.hasLoaded && state.documents.isEmpty) {
return DocumentsEmptyState(state: state);
}
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) {
return CustomScrollView(
controller: pagingScrollController,
slivers: [
SliverAdaptiveDocumentsView(
documents: state.documents,
hasInternetConnection: connectivity.isConnected,
isLabelClickable: false,
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: (document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
},
viewType: state.viewType,
),
if (state.hasLoaded && state.isLoading)
const SliverToBoxAdapter(
child: Center(
child: CircularProgressIndicator(),
),
)
],
);
},
);
},
),
);
}
}

View File

@@ -1,9 +1,14 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:paperless_mobile/generated/l10n.dart';
part 'application_settings_cubit.g.dart';
part 'application_settings_state.dart';
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
final LocalAuthenticationService _localAuthenticationService;
@@ -33,11 +38,6 @@ class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
_updateSettings(updatedSettings);
}
void setViewType(ViewType viewType) {
final updatedSettings = state.copyWith(preferredViewType: viewType);
_updateSettings(updatedSettings);
}
void setColorSchemeOption(ColorSchemeOption schemeOption) {
final updatedSettings =
state.copyWith(preferredColorSchemeOption: schemeOption);

View File

@@ -1,12 +1,4 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:paperless_mobile/generated/l10n.dart';
part 'application_settings_state.g.dart';
part of 'application_settings_cubit.dart';
///
/// State holding the current application settings such as selected language, theme mode and more.
@@ -20,14 +12,12 @@ class ApplicationSettingsState {
final bool isLocalAuthenticationEnabled;
final String preferredLocaleSubtag;
final ThemeMode preferredThemeMode;
final ViewType preferredViewType;
final ColorSchemeOption preferredColorSchemeOption;
ApplicationSettingsState({
required this.preferredLocaleSubtag,
this.preferredThemeMode = ThemeMode.system,
this.isLocalAuthenticationEnabled = false,
this.preferredViewType = ViewType.list,
this.preferredColorSchemeOption = ColorSchemeOption.classic,
});
@@ -39,7 +29,6 @@ class ApplicationSettingsState {
bool? isLocalAuthenticationEnabled,
String? preferredLocaleSubtag,
ThemeMode? preferredThemeMode,
ViewType? preferredViewType,
ColorSchemeOption? preferredColorSchemeOption,
}) {
return ApplicationSettingsState(
@@ -48,7 +37,6 @@ class ApplicationSettingsState {
preferredLocaleSubtag:
preferredLocaleSubtag ?? this.preferredLocaleSubtag,
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
preferredViewType: preferredViewType ?? this.preferredViewType,
preferredColorSchemeOption:
preferredColorSchemeOption ?? this.preferredColorSchemeOption,
);

View File

@@ -8,8 +8,8 @@ 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/hint_card.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
@@ -48,14 +48,14 @@ class AccountSettingsDialog extends StatelessWidget {
),
],
),
Divider(),
const Divider(),
ListTile(
dense: true,
leading: const Icon(Icons.person_add_rounded),
title: Text(S.of(context).accountSettingsAddAnotherAccount),
onTap: () {},
),
Divider(),
const Divider(),
FilledButton(
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(

View File

@@ -1,22 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.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/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/view/pages/application_settings_page.dart';
import 'package:paperless_mobile/features/settings/view/pages/security_settings_page.dart';
import 'package:paperless_mobile/features/settings/view/pages/storage_settings_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});

View File

@@ -1,10 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:provider/provider.dart';
class BiometricAuthenticationSetting extends StatelessWidget {
const BiometricAuthenticationSetting({super.key});

View File

@@ -2,16 +2,13 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:provider/provider.dart';
class ColorSchemeOptionSetting extends StatelessWidget {
const ColorSchemeOptionSetting({super.key});

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
import 'package:paperless_mobile/generated/l10n.dart';

View File

@@ -1,13 +1,13 @@
import 'package:bloc/bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'similar_documents_state.dart';
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
with PagedDocumentsMixin {
with DocumentPagingBlocMixin {
final int documentId;
@override

View File

@@ -1,6 +1,6 @@
part of 'similar_documents_cubit.dart';
class SimilarDocumentsState extends PagedDocumentsState {
class SimilarDocumentsState extends DocumentPagingState {
const SimilarDocumentsState({
super.filter,
super.hasLoaded,

View File

@@ -2,10 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart';
@@ -17,13 +16,14 @@ class SimilarDocumentsView extends StatefulWidget {
State<SimilarDocumentsView> createState() => _SimilarDocumentsViewState();
}
class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
final _scrollController = ScrollController();
class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
with DocumentPagingViewMixin {
@override
final pagingScrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_listenForLoadNewData);
try {
context.read<SimilarDocumentsCubit>().initialize();
} on PaperlessServerException catch (error, stackTrace) {
@@ -31,26 +31,6 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
}
}
@override
void dispose() {
_scrollController.removeListener(_listenForLoadNewData);
super.dispose();
}
void _listenForLoadNewData() async {
final currState = context.read<SimilarDocumentsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<SimilarDocumentsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
@@ -70,7 +50,7 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) {
return CustomScrollView(
controller: _scrollController,
controller: pagingScrollController,
slivers: [
SliverAdaptiveDocumentsView(
documents: state.documents,