mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 20:07:51 -06:00
Feat: Update scanner persistence, more migrations and bugfixes
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -20,6 +21,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/document_previe
|
||||
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/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
@@ -199,6 +201,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
documentId: state.document.id,
|
||||
),
|
||||
child: Padding(
|
||||
@@ -322,28 +325,45 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return BottomAppBar(
|
||||
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectivityState) {
|
||||
final isConnected = connectivityState.isConnected;
|
||||
final currentUser = context.watch<LocalUserAccount>();
|
||||
final canDelete =
|
||||
isConnected && currentUser.paperlessUser.canDeleteDocuments;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.deleteDocumentTooltip,
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed:
|
||||
canDelete ? () => _onDelete(state.document) : null,
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
DocumentDownloadButton(
|
||||
document: state.document,
|
||||
enabled: isConnected,
|
||||
ConnectivityAwareActionWrapper(
|
||||
disabled: !currentUser.paperlessUser.canDeleteDocuments,
|
||||
offlineBuilder: (context, child) {
|
||||
return const IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: null,
|
||||
).paddedSymmetrically(horizontal: 4);
|
||||
},
|
||||
child: IconButton(
|
||||
tooltip: S.of(context)!.deleteDocumentTooltip,
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => _onDelete(state.document),
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) =>
|
||||
const DocumentDownloadButton(
|
||||
document: null,
|
||||
enabled: false,
|
||||
),
|
||||
child: DocumentDownloadButton(
|
||||
document: state.document,
|
||||
),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) => const IconButton(
|
||||
icon: Icon(Icons.open_in_new),
|
||||
onPressed: null,
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: S.of(context)!.openInSystemViewer,
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
onPressed: _onOpenFileInSystemViewer,
|
||||
).paddedOnly(right: 4.0),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.openInSystemViewer,
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
onPressed: isConnected ? _onOpenFileInSystemViewer : null,
|
||||
).paddedOnly(right: 4.0),
|
||||
DocumentShareButton(document: state.document),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.print,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:paperless_mobile/features/document_details/cubit/document_detail
|
||||
import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/file_download_type.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/permission_helpers.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
@@ -34,19 +35,25 @@ class _DocumentShareButtonState extends State<DocumentShareButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: S.of(context)!.shareTooltip,
|
||||
icon: _isDownloadPending
|
||||
? const SizedBox(
|
||||
height: 16,
|
||||
width: 16,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const Icon(Icons.share),
|
||||
onPressed: widget.document != null && widget.enabled
|
||||
? () => _onShare(widget.document!)
|
||||
: null,
|
||||
).paddedOnly(right: 4);
|
||||
return ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) => const IconButton(
|
||||
icon: Icon(Icons.share),
|
||||
onPressed: null,
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: S.of(context)!.shareTooltip,
|
||||
icon: _isDownloadPending
|
||||
? const SizedBox(
|
||||
height: 16,
|
||||
width: 16,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const Icon(Icons.share),
|
||||
onPressed: widget.document != null && widget.enabled
|
||||
? () => _onShare(widget.document!)
|
||||
: null,
|
||||
).paddedOnly(right: 4),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onShare(DocumentModel document) async {
|
||||
|
||||
@@ -1,43 +1,71 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
part 'document_scanner_state.dart';
|
||||
|
||||
class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
final LocalNotificationService _notificationService;
|
||||
|
||||
DocumentScannerCubit(this._notificationService) : super(const []);
|
||||
DocumentScannerCubit(this._notificationService)
|
||||
: super(const InitialDocumentScannerState());
|
||||
|
||||
void addScan(File file) => emit([...state, file]);
|
||||
|
||||
void removeScan(int fileIndex) {
|
||||
try {
|
||||
state[fileIndex].deleteSync();
|
||||
final scans = [...state];
|
||||
scans.removeAt(fileIndex);
|
||||
emit(scans);
|
||||
} catch (_) {
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
Future<void> initialize() async {
|
||||
debugPrint("Restoring scans...");
|
||||
emit(const RestoringDocumentScannerState());
|
||||
final tempDir = await FileService.temporaryScansDirectory;
|
||||
final allFiles = tempDir.list().whereType<File>();
|
||||
final scans =
|
||||
await allFiles.where((event) => event.path.endsWith(".jpeg")).toList();
|
||||
debugPrint("Restored ${scans.length} scans.");
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
void addScan(File file) async {
|
||||
emit(LoadedDocumentScannerState(
|
||||
scans: [...state.scans, file],
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> removeScan(File file) async {
|
||||
try {
|
||||
for (final doc in state) {
|
||||
doc.deleteSync();
|
||||
if (kDebugMode) {
|
||||
log('[ScannerCubit]: Removed ${doc.path}');
|
||||
}
|
||||
}
|
||||
await file.delete();
|
||||
} catch (error, stackTrace) {
|
||||
throw InfoMessageException(
|
||||
code: ErrorCode.scanRemoveFailed,
|
||||
message: error.toString(),
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
final scans = state.scans..remove(file);
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
try {
|
||||
Future.wait([
|
||||
for (final file in state.scans) file.delete(),
|
||||
]);
|
||||
imageCache.clear();
|
||||
emit([]);
|
||||
} catch (_) {
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
} finally {
|
||||
emit(const InitialDocumentScannerState());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
lib/features/document_scan/cubit/document_scanner_state.dart
Normal file
30
lib/features/document_scan/cubit/document_scanner_state.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
part of 'document_scanner_cubit.dart';
|
||||
|
||||
sealed class DocumentScannerState {
|
||||
final List<File> scans;
|
||||
|
||||
const DocumentScannerState({
|
||||
this.scans = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class InitialDocumentScannerState extends DocumentScannerState {
|
||||
const InitialDocumentScannerState();
|
||||
}
|
||||
|
||||
class RestoringDocumentScannerState extends DocumentScannerState {
|
||||
const RestoringDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class LoadedDocumentScannerState extends DocumentScannerState {
|
||||
const LoadedDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class ErrorDocumentScannerState extends DocumentScannerState {
|
||||
final String message;
|
||||
|
||||
const ErrorDocumentScannerState({
|
||||
required this.message,
|
||||
super.scans,
|
||||
});
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
@@ -25,6 +24,7 @@ import 'package:paperless_mobile/features/document_upload/view/document_upload_p
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/permission_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart';
|
||||
@@ -52,66 +52,54 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
return SafeArea(
|
||||
top: true,
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: "fab_document_edit",
|
||||
onPressed: () => _openDocumentScanner(context),
|
||||
child: const Icon(Icons.add_a_photo_outlined),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.scanner,
|
||||
),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: actionsHandle,
|
||||
sliver: SliverPinnedHeader(
|
||||
child: _buildActions(connectedState.isConnected),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
if (state.isEmpty) {
|
||||
return SizedBox.expand(
|
||||
child: Center(
|
||||
child: _buildEmptyState(
|
||||
connectedState.isConnected,
|
||||
state,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return _buildImageGrid(state);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
return SafeArea(
|
||||
top: true,
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: "fab_document_edit",
|
||||
onPressed: () => _openDocumentScanner(context),
|
||||
child: const Icon(Icons.add_a_photo_outlined),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
titleText: S.of(context)!.scanner,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: actionsHandle,
|
||||
sliver: SliverPinnedHeader(
|
||||
child: _buildActions(),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, DocumentScannerState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
InitialDocumentScannerState() => _buildEmptyState(),
|
||||
RestoringDocumentScannerState() => Center(
|
||||
child: Text("Restoring..."),
|
||||
),
|
||||
LoadedDocumentScannerState() => _buildImageGrid(state.scans),
|
||||
ErrorDocumentScannerState() => Placeholder(),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActions(bool isConnected) {
|
||||
Widget _buildActions() {
|
||||
return ColoredBox(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: SizedBox(
|
||||
height: kTextTabBarHeight,
|
||||
child: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
child: BlocBuilder<DocumentScannerCubit, DocumentScannerState>(
|
||||
builder: (context, state) {
|
||||
return RawScrollbar(
|
||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 4),
|
||||
@@ -130,12 +118,12 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
),
|
||||
onPressed: state.isNotEmpty
|
||||
onPressed: state.scans.isNotEmpty
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DocumentView(
|
||||
documentBytes: _assembleFileBytes(
|
||||
state,
|
||||
state.scans,
|
||||
forcePdf: true,
|
||||
).then((file) => file.bytes),
|
||||
),
|
||||
@@ -150,19 +138,32 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
),
|
||||
onPressed: state.isEmpty ? null : () => _reset(context),
|
||||
onPressed:
|
||||
state.scans.isEmpty ? null : () => _reset(context),
|
||||
icon: const Icon(Icons.delete_sweep_outlined),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
label: Text(S.of(context)!.upload),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) {
|
||||
return TextButton.icon(
|
||||
label: Text(S.of(context)!.upload),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
);
|
||||
},
|
||||
disabled: state.scans.isEmpty,
|
||||
child: TextButton.icon(
|
||||
label: Text(S.of(context)!.upload),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
),
|
||||
onPressed: () =>
|
||||
_onPrepareDocumentUpload(context, state.scans),
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
),
|
||||
onPressed: state.isEmpty || !isConnected
|
||||
? null
|
||||
: () => _onPrepareDocumentUpload(context),
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
@@ -170,7 +171,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(5, 10, 5, 10),
|
||||
),
|
||||
onPressed: state.isEmpty ? null : _onSaveToFile,
|
||||
onPressed: state.scans.isEmpty ? null : _onSaveToFile,
|
||||
icon: const Icon(Icons.save_alt_outlined),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
@@ -192,7 +193,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
final cubit = context.read<DocumentScannerCubit>();
|
||||
final file = await _assembleFileBytes(
|
||||
forcePdf: true,
|
||||
context.read<DocumentScannerCubit>().state,
|
||||
context.read<DocumentScannerCubit>().state.scans,
|
||||
);
|
||||
try {
|
||||
final globalSettings =
|
||||
@@ -249,9 +250,9 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
context.read<DocumentScannerCubit>().addScan(file);
|
||||
}
|
||||
|
||||
void _onPrepareDocumentUpload(BuildContext context) async {
|
||||
void _onPrepareDocumentUpload(BuildContext context, List<File> scans) async {
|
||||
final file = await _assembleFileBytes(
|
||||
context.read<DocumentScannerCubit>().state,
|
||||
scans,
|
||||
forcePdf: Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.enforceSinglePagePdfUpload,
|
||||
@@ -269,10 +270,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(bool isConnected, List<File> scans) {
|
||||
if (scans.isNotEmpty) {
|
||||
return _buildImageGrid(scans);
|
||||
}
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
@@ -288,9 +286,15 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
onPressed: () => _openDocumentScanner(context),
|
||||
),
|
||||
Text(S.of(context)!.or),
|
||||
TextButton(
|
||||
child: Text(S.of(context)!.uploadADocumentFromThisDevice),
|
||||
onPressed: isConnected ? _onUploadFromFilesystem : null,
|
||||
ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) => TextButton(
|
||||
child: Text(S.of(context)!.uploadADocumentFromThisDevice),
|
||||
onPressed: null,
|
||||
),
|
||||
child: TextButton(
|
||||
child: Text(S.of(context)!.uploadADocumentFromThisDevice),
|
||||
onPressed: _onUploadFromFilesystem,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -318,7 +322,9 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
file: scans[index],
|
||||
onDelete: () async {
|
||||
try {
|
||||
context.read<DocumentScannerCubit>().removeScan(index);
|
||||
context
|
||||
.read<DocumentScannerCubit>()
|
||||
.removeScan(scans[index]);
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
@@ -119,4 +120,9 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState>
|
||||
|
||||
@override
|
||||
Future<void> onFilterUpdated(DocumentFilter filter) async {}
|
||||
|
||||
@override
|
||||
// TODO: implement connectivityStatusService
|
||||
ConnectivityStatusService get connectivityStatusService =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
|
||||
part 'document_upload_state.dart';
|
||||
|
||||
@@ -13,12 +13,12 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
final PaperlessDocumentsApi _documentApi;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
final Connectivity _connectivity;
|
||||
final ConnectivityStatusService _connectivityStatusService;
|
||||
|
||||
DocumentUploadCubit(
|
||||
this._labelRepository,
|
||||
this._documentApi,
|
||||
this._connectivity,
|
||||
this._connectivityStatusService,
|
||||
) : super(const DocumentUploadState()) {
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
@@ -20,6 +21,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
@@ -31,6 +34,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this._userState,
|
||||
this.connectivityStatusService,
|
||||
) : super(DocumentsState(
|
||||
filter: _userState.currentDocumentFilter,
|
||||
viewType: _userState.documentsPageViewType,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/services.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/database/tables/local_user_account.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/sliver_search_bar.dart';
|
||||
@@ -333,12 +334,16 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: savedViewsHandle),
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.filter != current.filter,
|
||||
builder: (context, state) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SavedViewsWidget(
|
||||
SliverToBoxAdapter(
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.filter != current.filter,
|
||||
builder: (context, state) {
|
||||
final currentUser = context.watch<LocalUserAccount>();
|
||||
if (!currentUser.paperlessUser.canViewSavedViews) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return SavedViewsWidget(
|
||||
controller: _savedViewsExpansionController,
|
||||
onViewSelected: (view) {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
@@ -372,9 +377,9 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
}
|
||||
},
|
||||
filter: state.filter,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
@@ -28,17 +29,17 @@ class DocumentPreview extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: isClickable
|
||||
? () {
|
||||
DocumentPreviewRoute($extra: document).push(context);
|
||||
}
|
||||
: null,
|
||||
child: HeroMode(
|
||||
enabled: enableHero,
|
||||
child: Hero(
|
||||
tag: "thumb_${document.id}",
|
||||
child: _buildPreview(context),
|
||||
return ConnectivityAwareActionWrapper(
|
||||
child: GestureDetector(
|
||||
onTap: isClickable
|
||||
? () => DocumentPreviewRoute($extra: document).push(context)
|
||||
: null,
|
||||
child: HeroMode(
|
||||
enabled: enableHero,
|
||||
child: Hero(
|
||||
tag: "thumb_${document.id}",
|
||||
child: _buildPreview(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_view_chip.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/saved_views_route.dart';
|
||||
|
||||
class SavedViewsWidget extends StatefulWidget {
|
||||
@@ -146,15 +147,17 @@ class _SavedViewsWidgetState extends State<SavedViewsWidget>
|
||||
final isSelected =
|
||||
(widget.filter.selectedView ?? -1) ==
|
||||
view.id;
|
||||
return SavedViewChip(
|
||||
view: view,
|
||||
onViewSelected: widget.onViewSelected,
|
||||
selected: isSelected,
|
||||
hasChanged: isSelected &&
|
||||
view.toDocumentFilter() !=
|
||||
widget.filter,
|
||||
onUpdateView: widget.onUpdateView,
|
||||
onDeleteView: widget.onDeleteView,
|
||||
return ConnectivityAwareActionWrapper(
|
||||
child: SavedViewChip(
|
||||
view: view,
|
||||
onViewSelected: widget.onViewSelected,
|
||||
selected: isSelected,
|
||||
hasChanged: isSelected &&
|
||||
view.toDocumentFilter() !=
|
||||
widget.filter,
|
||||
onUpdateView: widget.onUpdateView,
|
||||
onDeleteView: widget.onDeleteView,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) =>
|
||||
@@ -178,12 +181,14 @@ class _SavedViewsWidgetState extends State<SavedViewsWidget>
|
||||
alignment: Alignment.centerRight,
|
||||
child: Tooltip(
|
||||
message: S.of(context)!.createFromCurrentFilter,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
CreateSavedViewRoute(widget.filter).push(context);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(S.of(context)!.newView),
|
||||
child: ConnectivityAwareActionWrapper(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
CreateSavedViewRoute(widget.filter).push(context);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(S.of(context)!.newView),
|
||||
),
|
||||
),
|
||||
).padded(4),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:paperless_api/paperless_api.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/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
|
||||
enum DateRangeSelection { before, after }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:paperless_mobile/core/translation/sort_field_localization_mapper
|
||||
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/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
|
||||
class SortDocumentsButton extends StatelessWidget {
|
||||
final bool enabled;
|
||||
@@ -20,55 +21,65 @@ class SortDocumentsButton extends StatelessWidget {
|
||||
if (state.filter.sortField == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
print(state.filter.sortField);
|
||||
return TextButton.icon(
|
||||
icon: Icon(state.filter.sortOrder == SortOrder.ascending
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward),
|
||||
label: Text(translateSortField(context, state.filter.sortField)),
|
||||
onPressed: enabled
|
||||
? () {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||
value: context.read<DocumentsCubit>(),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit(context.read()),
|
||||
),
|
||||
],
|
||||
child: SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) {
|
||||
return context
|
||||
.read<DocumentsCubit>()
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
),
|
||||
);
|
||||
},
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
final icon = Icon(state.filter.sortOrder == SortOrder.ascending
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward);
|
||||
final label = Text(translateSortField(context, state.filter.sortField));
|
||||
return ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) {
|
||||
return TextButton.icon(
|
||||
icon: icon,
|
||||
label: label,
|
||||
onPressed: null,
|
||||
);
|
||||
},
|
||||
child: TextButton.icon(
|
||||
icon: icon,
|
||||
label: label,
|
||||
onPressed: enabled
|
||||
? () {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||
value: context.read<DocumentsCubit>(),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit(context.read()),
|
||||
),
|
||||
],
|
||||
child: SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) {
|
||||
return context
|
||||
.read<DocumentsCubit>()
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
),
|
||||
);
|
||||
},
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -160,11 +160,13 @@ class HomeShellWidget extends StatelessWidget {
|
||||
Hive.box<LocalUserAppState>(
|
||||
HiveBoxes.localUserAppState)
|
||||
.get(currentUserId)!,
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
DocumentScannerCubit(context.read()),
|
||||
DocumentScannerCubit(context.read())
|
||||
..initialize(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) {
|
||||
@@ -173,6 +175,7 @@ class HomeShellWidget extends StatelessWidget {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
);
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewDocuments &&
|
||||
|
||||
@@ -7,6 +7,7 @@ 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/core/repository/label_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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';
|
||||
|
||||
@@ -18,7 +19,8 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
final PaperlessDocumentsApi _documentsApi;
|
||||
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
@@ -32,6 +34,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
this._statsApi,
|
||||
this._labelRepository,
|
||||
this.notifier,
|
||||
this.connectivityStatusService,
|
||||
) : super(InboxState(
|
||||
labels: _labelRepository.state,
|
||||
)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.
|
||||
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/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
class InboxPage extends StatefulWidget {
|
||||
@@ -74,45 +76,50 @@ class _InboxPageState extends State<InboxPage>
|
||||
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded || state.documents.isEmpty || !canEditDocument) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FloatingActionButton.extended(
|
||||
extendedPadding: _showExtendedFab
|
||||
? null
|
||||
: const EdgeInsets.symmetric(horizontal: 16),
|
||||
heroTag: "inbox_page_fab",
|
||||
label: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axis: Axis.horizontal,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _showExtendedFab
|
||||
? Row(
|
||||
children: [
|
||||
const Icon(Icons.done_all),
|
||||
Text(S.of(context)!.allSeen),
|
||||
],
|
||||
)
|
||||
: const Icon(Icons.done_all),
|
||||
),
|
||||
onPressed: state.hasLoaded && state.documents.isNotEmpty
|
||||
? () => _onMarkAllAsSeen(
|
||||
state.documents,
|
||||
state.inboxTags,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
floatingActionButton: ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) => const SizedBox.shrink(),
|
||||
child: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded ||
|
||||
state.documents.isEmpty ||
|
||||
!canEditDocument) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FloatingActionButton.extended(
|
||||
extendedPadding: _showExtendedFab
|
||||
? null
|
||||
: const EdgeInsets.symmetric(horizontal: 16),
|
||||
heroTag: "inbox_page_fab",
|
||||
label: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axis: Axis.horizontal,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _showExtendedFab
|
||||
? Row(
|
||||
children: [
|
||||
const Icon(Icons.done_all),
|
||||
Text(S.of(context)!.allSeen),
|
||||
],
|
||||
)
|
||||
: const Icon(Icons.done_all),
|
||||
),
|
||||
onPressed: state.hasLoaded && state.documents.isNotEmpty
|
||||
? () => _onMarkAllAsSeen(
|
||||
state.documents,
|
||||
state.inboxTags,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: true,
|
||||
@@ -268,6 +275,12 @@ class _InboxPageState extends State<InboxPage>
|
||||
showSnackBar(context, S.of(context)!.missingPermissions);
|
||||
return false;
|
||||
}
|
||||
final isConnectedToInternet =
|
||||
await context.read<ConnectivityStatusService>().isConnectedToInternet();
|
||||
if (!isConnectedToInternet) {
|
||||
showSnackBar(context, S.of(context)!.youAreCurrentlyOffline);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
final removedTags = await context.read<InboxCubit>().removeFromInbox(doc);
|
||||
showSnackBar(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
@@ -16,6 +14,7 @@ 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/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class InboxItemPlaceholder extends StatelessWidget {
|
||||
@@ -228,7 +227,9 @@ class _InboxItemState extends State<InboxItem> {
|
||||
),
|
||||
LimitedBox(
|
||||
maxHeight: 56,
|
||||
child: _buildActions(context),
|
||||
child: ConnectivityAwareActionWrapper(
|
||||
child: _buildActions(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddedOnly(left: 8, top: 8, bottom: 8),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:paperless_mobile/features/document_search/view/sliver_search_bar
|
||||
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/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
|
||||
|
||||
class LabelsPage extends StatefulWidget {
|
||||
@@ -66,36 +67,37 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
.getValue()!
|
||||
.loggedInUserId;
|
||||
final user = box.get(currentUserId)!.paperlessUser;
|
||||
|
||||
final fabLabel = [
|
||||
S.of(context)!.addCorrespondent,
|
||||
S.of(context)!.addDocumentType,
|
||||
S.of(context)!.addTag,
|
||||
S.of(context)!.addStoragePath,
|
||||
][_currentIndex];
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
heroTag: "inbox_page_fab",
|
||||
label: Text(
|
||||
[
|
||||
S.of(context)!.addCorrespondent,
|
||||
S.of(context)!.addDocumentType,
|
||||
S.of(context)!.addTag,
|
||||
S.of(context)!.addStoragePath,
|
||||
floatingActionButton: ConnectivityAwareActionWrapper(
|
||||
offlineBuilder: (context, child) => const SizedBox.shrink(),
|
||||
child: FloatingActionButton.extended(
|
||||
heroTag: "inbox_page_fab",
|
||||
label: Text(fabLabel),
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: [
|
||||
if (user.canViewCorrespondents)
|
||||
() => CreateLabelRoute(LabelType.correspondent)
|
||||
.push(context),
|
||||
if (user.canViewDocumentTypes)
|
||||
() => CreateLabelRoute(LabelType.documentType)
|
||||
.push(context),
|
||||
if (user.canViewTags)
|
||||
() => CreateLabelRoute(LabelType.tag).push(context),
|
||||
if (user.canViewStoragePaths)
|
||||
() => CreateLabelRoute(LabelType.storagePath)
|
||||
.push(context),
|
||||
][_currentIndex],
|
||||
),
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: [
|
||||
if (user.canViewCorrespondents)
|
||||
() => CreateLabelRoute(LabelType.correspondent)
|
||||
.push(context),
|
||||
if (user.canViewDocumentTypes)
|
||||
() => CreateLabelRoute(LabelType.documentType)
|
||||
.push(context),
|
||||
if (user.canViewTags)
|
||||
() => CreateLabelRoute(LabelType.tag).push(context),
|
||||
if (user.canViewStoragePaths)
|
||||
() => CreateLabelRoute(LabelType.storagePath)
|
||||
.push(context),
|
||||
][_currentIndex],
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
|
||||
@@ -44,7 +44,7 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectivityState) {
|
||||
if (!connectivityState.isConnected) {
|
||||
return const OfflineWidget();
|
||||
return const SliverFillRemaining(child: OfflineWidget());
|
||||
}
|
||||
final sortedLabels = labels.values.toList()..sort();
|
||||
if (labels.isEmpty) {
|
||||
|
||||
@@ -22,6 +22,7 @@ class LandingPage extends StatefulWidget {
|
||||
|
||||
class _LandingPageState extends State<LandingPage> {
|
||||
final _searchBarHandle = SliverOverlapAbsorberHandle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
@@ -121,7 +122,6 @@ class _LandingPageState extends State<LandingPage> {
|
||||
|
||||
Widget _buildStatisticsCard(BuildContext context) {
|
||||
final currentUser = context.read<LocalUserAccount>().paperlessUser;
|
||||
|
||||
return ExpansionCard(
|
||||
initiallyExpanded: false,
|
||||
title: Text(
|
||||
|
||||
@@ -3,6 +3,7 @@ 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/core/service/connectivity_status_service.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';
|
||||
@@ -14,7 +15,8 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
|
||||
with DocumentPagingBlocMixin {
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
@@ -25,6 +27,7 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService,
|
||||
) : super(LinkedDocumentsState(filter: filter)) {
|
||||
updateFilter(filter: filter);
|
||||
_labelRepository.addListener(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
@@ -12,25 +14,28 @@ import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart'
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
part 'authentication_cubit.freezed.dart';
|
||||
part 'authentication_state.dart';
|
||||
|
||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final LocalAuthenticationService _localAuthService;
|
||||
final PaperlessApiFactory _apiFactory;
|
||||
final SessionManager _sessionManager;
|
||||
final ConnectivityStatusService _connectivityService;
|
||||
|
||||
AuthenticationCubit(
|
||||
this._localAuthService,
|
||||
this._apiFactory,
|
||||
this._sessionManager,
|
||||
) : super(const AuthenticationState.unauthenticated());
|
||||
this._connectivityService,
|
||||
) : super(const UnauthenticatedState());
|
||||
|
||||
Future<void> login({
|
||||
required LoginFormCredentials credentials,
|
||||
@@ -51,8 +56,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
_sessionManager,
|
||||
);
|
||||
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
|
||||
// Mark logged in user as currently active user.
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
@@ -60,7 +63,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
await globalSettings.save();
|
||||
|
||||
emit(
|
||||
AuthenticationState.authenticated(
|
||||
AuthenticatedState(
|
||||
localUserId: localUserId,
|
||||
),
|
||||
);
|
||||
@@ -72,11 +75,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
|
||||
/// Switches to another account if it exists.
|
||||
Future<void> switchAccount(String localUserId) async {
|
||||
emit(const AuthenticationState.switchingAccounts());
|
||||
emit(const SwitchingAccountsState());
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
if (globalSettings.loggedInUserId == localUserId) {
|
||||
emit(AuthenticationState.authenticated(localUserId: localUserId));
|
||||
emit(AuthenticatedState(localUserId: localUserId));
|
||||
return;
|
||||
}
|
||||
final userAccountBox =
|
||||
@@ -125,7 +128,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
apiVersion,
|
||||
);
|
||||
|
||||
emit(AuthenticationState.authenticated(
|
||||
emit(AuthenticatedState(
|
||||
localUserId: localUserId,
|
||||
));
|
||||
});
|
||||
@@ -182,7 +185,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
"There is nothing to restore.",
|
||||
);
|
||||
// If there is nothing to restore, we can quit here.
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
emit(const UnauthenticatedState());
|
||||
return;
|
||||
}
|
||||
final localUserAccountBox =
|
||||
@@ -203,7 +206,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final localAuthSuccess =
|
||||
await _localAuthService.authenticateLocalUser(authenticationMesage);
|
||||
if (!localAuthSuccess) {
|
||||
emit(const AuthenticationState.requriresLocalAuthentication());
|
||||
emit(const RequiresLocalAuthenticationState());
|
||||
_debugPrintMessage(
|
||||
"restoreSessionState",
|
||||
"User could not be authenticated.",
|
||||
@@ -239,14 +242,17 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
"User should be authenticated but no authentication information was found.",
|
||||
);
|
||||
}
|
||||
|
||||
_debugPrintMessage(
|
||||
"restoreSessionState",
|
||||
"Authentication credentials successfully retrieved.",
|
||||
);
|
||||
|
||||
_debugPrintMessage(
|
||||
"restoreSessionState",
|
||||
"Updating current session state...",
|
||||
);
|
||||
|
||||
_sessionManager.updateSettings(
|
||||
clientCertificate: authentication.clientCertificate,
|
||||
authToken: authentication.token,
|
||||
@@ -256,18 +262,32 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
"restoreSessionState",
|
||||
"Current session state successfully updated.",
|
||||
);
|
||||
final hasInternetConnection =
|
||||
await _connectivityService.isConnectedToInternet();
|
||||
if (hasInternetConnection) {
|
||||
_debugPrintMessage(
|
||||
"restoreSessionMState",
|
||||
"Updating server user...",
|
||||
);
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
await _updateRemoteUser(
|
||||
_sessionManager,
|
||||
localUserAccount,
|
||||
apiVersion,
|
||||
);
|
||||
_debugPrintMessage(
|
||||
"restoreSessionMState",
|
||||
"Successfully updated server user.",
|
||||
);
|
||||
} else {
|
||||
_debugPrintMessage(
|
||||
"restoreSessionMState",
|
||||
"Skipping update of server user (no internet connection).",
|
||||
);
|
||||
}
|
||||
|
||||
emit(AuthenticatedState(localUserId: localUserId));
|
||||
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
await _updateRemoteUser(
|
||||
_sessionManager,
|
||||
localUserAccount,
|
||||
apiVersion,
|
||||
);
|
||||
emit(
|
||||
AuthenticationState.authenticated(
|
||||
localUserId: localUserId,
|
||||
),
|
||||
);
|
||||
_debugPrintMessage(
|
||||
"restoreSessionState",
|
||||
"Session was successfully restored.",
|
||||
@@ -285,7 +305,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
globalSettings.loggedInUserId = null;
|
||||
await globalSettings.save();
|
||||
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
emit(const UnauthenticatedState());
|
||||
_debugPrintMessage(
|
||||
"logout",
|
||||
"User successfully logged out.",
|
||||
@@ -353,7 +373,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
"_addUser",
|
||||
"An error occurred! The user $localUserId already exists.",
|
||||
);
|
||||
throw Exception("User already exists!");
|
||||
throw InfoMessageException(code: ErrorCode.userAlreadyExists);
|
||||
}
|
||||
final apiVersion = await _getApiVersion(sessionManager.client);
|
||||
_debugPrintMessage(
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
part of 'authentication_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class AuthenticationState with _$AuthenticationState {
|
||||
const AuthenticationState._();
|
||||
sealed class AuthenticationState {
|
||||
const AuthenticationState();
|
||||
|
||||
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
||||
const factory AuthenticationState.requriresLocalAuthentication() =
|
||||
_RequiresLocalAuthentication;
|
||||
const factory AuthenticationState.authenticated({
|
||||
required String localUserId,
|
||||
}) = _Authenticated;
|
||||
const factory AuthenticationState.switchingAccounts() = _SwitchingAccounts;
|
||||
|
||||
bool get isAuthenticated => maybeWhen(
|
||||
authenticated: (_) => true,
|
||||
orElse: () => false,
|
||||
);
|
||||
bool get isAuthenticated =>
|
||||
switch (this) { AuthenticatedState() => true, _ => false };
|
||||
}
|
||||
|
||||
class UnauthenticatedState extends AuthenticationState {
|
||||
const UnauthenticatedState();
|
||||
}
|
||||
|
||||
class RequiresLocalAuthenticationState extends AuthenticationState {
|
||||
const RequiresLocalAuthenticationState();
|
||||
}
|
||||
|
||||
class AuthenticatedState extends AuthenticationState {
|
||||
final String localUserId;
|
||||
|
||||
const AuthenticatedState({
|
||||
required this.localUserId,
|
||||
});
|
||||
}
|
||||
|
||||
class SwitchingAccountsState extends AuthenticationState {
|
||||
const SwitchingAccountsState();
|
||||
}
|
||||
|
||||
class AuthenticationErrorState extends AuthenticationState {
|
||||
const AuthenticationErrorState();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
@@ -153,6 +154,8 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
showErrorMessage(context, error);
|
||||
} on ServerMessageException catch (error) {
|
||||
showLocalizedError(context, error.message);
|
||||
} on InfoMessageException catch (error) {
|
||||
showInfoMessage(context, error);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,11 @@ class _ServerLoginPageState extends State<ServerLoginPage> {
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6)),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
],
|
||||
),
|
||||
@@ -64,11 +65,16 @@ class _ServerLoginPageState extends State<ServerLoginPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
setState(() => _isLoginLoading = true);
|
||||
await widget.onSubmit();
|
||||
setState(() => _isLoginLoading = false);
|
||||
},
|
||||
onPressed: !_isLoginLoading
|
||||
? () async {
|
||||
setState(() => _isLoginLoading = true);
|
||||
try {
|
||||
await widget.onSubmit();
|
||||
} finally {
|
||||
setState(() => _isLoginLoading = false);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(S.of(context)!.signIn),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -2,6 +2,8 @@ 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 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:rxdart/streams.dart';
|
||||
|
||||
import 'paged_documents_state.dart';
|
||||
|
||||
@@ -11,13 +13,16 @@ import 'paged_documents_state.dart';
|
||||
///
|
||||
mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||
on BlocBase<State> {
|
||||
ConnectivityStatusService get connectivityStatusService;
|
||||
PaperlessDocumentsApi get api;
|
||||
DocumentChangedNotifier get notifier;
|
||||
|
||||
Future<void> onFilterUpdated(DocumentFilter filter);
|
||||
|
||||
Future<void> loadMore() async {
|
||||
if (state.isLastPageLoaded) {
|
||||
final hasConnection =
|
||||
await connectivityStatusService.isConnectedToInternet();
|
||||
if (state.isLastPageLoaded || !hasConnection) {
|
||||
return;
|
||||
}
|
||||
emit(state.copyWithPaged(isLoading: true));
|
||||
@@ -47,6 +52,32 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||
Future<void> updateFilter({
|
||||
final DocumentFilter filter = const DocumentFilter(),
|
||||
}) async {
|
||||
final hasConnection =
|
||||
await connectivityStatusService.isConnectedToInternet();
|
||||
if (!hasConnection) {
|
||||
// Just filter currently loaded documents
|
||||
final filteredDocuments = state.value
|
||||
.expand((page) => page.results)
|
||||
.where((doc) => filter.matches(doc))
|
||||
.toList();
|
||||
emit(state.copyWithPaged(isLoading: true));
|
||||
|
||||
emit(
|
||||
state.copyWithPaged(
|
||||
filter: filter,
|
||||
value: [
|
||||
PagedSearchResult(
|
||||
results: filteredDocuments,
|
||||
count: filteredDocuments.length,
|
||||
next: null,
|
||||
previous: null,
|
||||
)
|
||||
],
|
||||
hasLoaded: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
emit(state.copyWithPaged(isLoading: true));
|
||||
final result = await api.findAll(filter.copyWith(page: 1));
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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';
|
||||
@@ -15,7 +16,8 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
@@ -27,7 +29,8 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this._userState, {
|
||||
this._userState,
|
||||
this.connectivityStatusService, {
|
||||
required this.savedView,
|
||||
int initialCount = 25,
|
||||
}) : super(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
|
||||
part 'saved_view_preview_state.dart';
|
||||
part 'saved_view_preview_cubit.freezed.dart';
|
||||
@@ -8,11 +9,21 @@ part 'saved_view_preview_cubit.freezed.dart';
|
||||
class SavedViewPreviewCubit extends Cubit<SavedViewPreviewState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
final SavedView view;
|
||||
SavedViewPreviewCubit(this._api, this.view)
|
||||
: super(const SavedViewPreviewState.initial());
|
||||
final ConnectivityStatusService _connectivityStatusService;
|
||||
SavedViewPreviewCubit(
|
||||
this._api,
|
||||
this._connectivityStatusService, {
|
||||
required this.view,
|
||||
}) : super(const InitialSavedViewPreviewState());
|
||||
|
||||
Future<void> initialize() async {
|
||||
emit(const SavedViewPreviewState.loading());
|
||||
final isConnected =
|
||||
await _connectivityStatusService.isConnectedToInternet();
|
||||
if (!isConnected) {
|
||||
emit(const OfflineSavedViewPreviewState());
|
||||
return;
|
||||
}
|
||||
emit(const LoadingSavedViewPreviewState());
|
||||
try {
|
||||
final documents = await _api.findAll(
|
||||
view.toDocumentFilter().copyWith(
|
||||
@@ -20,9 +31,9 @@ class SavedViewPreviewCubit extends Cubit<SavedViewPreviewState> {
|
||||
pageSize: 5,
|
||||
),
|
||||
);
|
||||
emit(SavedViewPreviewState.loaded(documents: documents.results));
|
||||
emit(LoadedSavedViewPreviewState(documents: documents.results));
|
||||
} catch (e) {
|
||||
emit(const SavedViewPreviewState.error());
|
||||
emit(const ErrorSavedViewPreviewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
part of 'saved_view_preview_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class SavedViewPreviewState with _$SavedViewPreviewState {
|
||||
const factory SavedViewPreviewState.initial() = _Initial;
|
||||
const factory SavedViewPreviewState.loading() = _Loading;
|
||||
const factory SavedViewPreviewState.loaded({
|
||||
required List<DocumentModel> documents,
|
||||
}) = _Loaded;
|
||||
const factory SavedViewPreviewState.error() = _Error;
|
||||
sealed class SavedViewPreviewState {
|
||||
const SavedViewPreviewState();
|
||||
}
|
||||
|
||||
class InitialSavedViewPreviewState extends SavedViewPreviewState {
|
||||
const InitialSavedViewPreviewState();
|
||||
}
|
||||
|
||||
class LoadingSavedViewPreviewState extends SavedViewPreviewState {
|
||||
const LoadingSavedViewPreviewState();
|
||||
}
|
||||
|
||||
class LoadedSavedViewPreviewState extends SavedViewPreviewState {
|
||||
final List<DocumentModel> documents;
|
||||
|
||||
const LoadedSavedViewPreviewState({
|
||||
required this.documents,
|
||||
});
|
||||
}
|
||||
|
||||
class ErrorSavedViewPreviewState extends SavedViewPreviewState {
|
||||
const ErrorSavedViewPreviewState();
|
||||
}
|
||||
|
||||
class OfflineSavedViewPreviewState extends SavedViewPreviewState {
|
||||
const OfflineSavedViewPreviewState();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,11 @@ class SavedViewPreview extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Provider(
|
||||
create: (context) =>
|
||||
SavedViewPreviewCubit(context.read(), savedView)..initialize(),
|
||||
create: (context) => SavedViewPreviewCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
view: savedView,
|
||||
)..initialize(),
|
||||
builder: (context, child) {
|
||||
return ExpansionCard(
|
||||
initiallyExpanded: expanded,
|
||||
@@ -33,34 +36,40 @@ class SavedViewPreview extends StatelessWidget {
|
||||
children: [
|
||||
BlocBuilder<SavedViewPreviewCubit, SavedViewPreviewState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
loaded: (documents) {
|
||||
if (documents.isEmpty) {
|
||||
return Text(S.of(context)!.noDocumentsFound).padded();
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
for (final document in documents)
|
||||
DocumentListItem(
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
isSelected: false,
|
||||
isSelectionActive: false,
|
||||
onTap: (document) {
|
||||
DocumentDetailsRoute($extra: document)
|
||||
.push(context);
|
||||
},
|
||||
onSelected: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
error: () => Text(S.of(context)!.couldNotLoadSavedViews),
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
).paddedOnly(top: 8, bottom: 24),
|
||||
);
|
||||
return switch (state) {
|
||||
LoadedSavedViewPreviewState(documents: var documents) =>
|
||||
Builder(
|
||||
builder: (context) {
|
||||
if (documents.isEmpty) {
|
||||
return Text(S.of(context)!.noDocumentsFound)
|
||||
.padded();
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
for (final document in documents)
|
||||
DocumentListItem(
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
isSelected: false,
|
||||
isSelectionActive: false,
|
||||
onTap: (document) {
|
||||
DocumentDetailsRoute($extra: document)
|
||||
.push(context);
|
||||
},
|
||||
onSelected: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
ErrorSavedViewPreviewState() =>
|
||||
Text(S.of(context)!.couldNotLoadSavedViews).padded(16),
|
||||
OfflineSavedViewPreviewState() =>
|
||||
Text(S.of(context)!.youAreCurrentlyOffline).padded(16),
|
||||
_ => const CircularProgressIndicator()
|
||||
.paddedOnly(top: 8, bottom: 24),
|
||||
};
|
||||
},
|
||||
),
|
||||
Row(
|
||||
|
||||
@@ -2,6 +2,7 @@ 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/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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';
|
||||
|
||||
@@ -10,7 +11,8 @@ part 'similar_documents_state.dart';
|
||||
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
with DocumentPagingBlocMixin {
|
||||
final int documentId;
|
||||
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
@@ -22,7 +24,8 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
SimilarDocumentsCubit(
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository, {
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService, {
|
||||
required this.documentId,
|
||||
}) : super(const SimilarDocumentsState(filter: DocumentFilter())) {
|
||||
notifier.addListener(
|
||||
|
||||
Reference in New Issue
Block a user