mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 14:07:59 -06:00
feat: Implement updated receive share logic
This commit is contained in:
72
lib/features/sharing/cubit/receive_share_cubit.dart
Normal file
72
lib/features/sharing/cubit/receive_share_cubit.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
part 'receive_share_state.dart';
|
||||
|
||||
class ConsumptionChangeNotifier extends ChangeNotifier {
|
||||
List<File> pendingFiles = [];
|
||||
|
||||
ConsumptionChangeNotifier();
|
||||
|
||||
Future<void> loadFromConsumptionDirectory({required String userId}) async {
|
||||
pendingFiles = await _getCurrentFiles(userId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Creates a local copy of all shared files and reloads all files
|
||||
/// from the user's consumption directory.
|
||||
Future<void> addFiles({
|
||||
required List<File> files,
|
||||
required String userId,
|
||||
}) async {
|
||||
if (files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final consumptionDirectory =
|
||||
await FileService.getConsumptionDirectory(userId: userId);
|
||||
for (final file in files) {
|
||||
File localFile;
|
||||
if (file.path.startsWith(consumptionDirectory.path)) {
|
||||
localFile = file;
|
||||
} else {
|
||||
final fileName = p.basename(file.path);
|
||||
localFile = File(p.join(consumptionDirectory.path, fileName));
|
||||
await file.copy(localFile.path);
|
||||
}
|
||||
}
|
||||
return loadFromConsumptionDirectory(userId: userId);
|
||||
}
|
||||
|
||||
/// Marks a file as processed by removing it from the queue and deleting the local copy of the file.
|
||||
Future<void> discardFile(
|
||||
File file, {
|
||||
required String userId,
|
||||
}) async {
|
||||
final consumptionDirectory =
|
||||
await FileService.getConsumptionDirectory(userId: userId);
|
||||
if (file.path.startsWith(consumptionDirectory.path)) {
|
||||
await file.delete();
|
||||
}
|
||||
return loadFromConsumptionDirectory(userId: userId);
|
||||
}
|
||||
|
||||
/// Returns the next file to process of null if no file exists.
|
||||
Future<File?> getNextFile({required String userId}) async {
|
||||
final files = await _getCurrentFiles(userId);
|
||||
if (files.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return files.first;
|
||||
}
|
||||
|
||||
Future<List<File>> _getCurrentFiles(String userId) async {
|
||||
final directory = await FileService.getConsumptionDirectory(userId: userId);
|
||||
final files = await FileService.getAllFiles(directory);
|
||||
return files;
|
||||
}
|
||||
}
|
||||
32
lib/features/sharing/cubit/receive_share_state.dart
Normal file
32
lib/features/sharing/cubit/receive_share_state.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
part of 'receive_share_cubit.dart';
|
||||
|
||||
sealed class ReceiveShareState {
|
||||
final List<File> files;
|
||||
|
||||
const ReceiveShareState({this.files = const []});
|
||||
}
|
||||
|
||||
class ReceiveShareStateInitial extends ReceiveShareState {
|
||||
const ReceiveShareStateInitial();
|
||||
}
|
||||
|
||||
class ReceiveShareStateLoading extends ReceiveShareState {
|
||||
const ReceiveShareStateLoading();
|
||||
}
|
||||
|
||||
class ReceiveShareStateLoaded extends ReceiveShareState {
|
||||
const ReceiveShareStateLoaded({super.files});
|
||||
|
||||
ReceiveShareStateLoaded copyWith({
|
||||
List<File>? files,
|
||||
}) {
|
||||
return ReceiveShareStateLoaded(
|
||||
files: files ?? this.files,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReceiveShareStateError extends ReceiveShareState {
|
||||
final String message;
|
||||
const ReceiveShareStateError(this.message);
|
||||
}
|
||||
73
lib/features/sharing/logic/upload_queue_processor.dart
Normal file
73
lib/features/sharing/logic/upload_queue_processor.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/sharing/model/share_intent_queue.dart';
|
||||
import 'package:paperless_mobile/features/sharing/view/dialog/discard_shared_file_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class UploadQueueProcessor {
|
||||
final ShareIntentQueue queue;
|
||||
|
||||
UploadQueueProcessor({required this.queue});
|
||||
|
||||
bool _isFileTypeSupported(File file) {
|
||||
final isSupported =
|
||||
supportedFileExtensions.contains(p.extension(file.path));
|
||||
return isSupported;
|
||||
}
|
||||
|
||||
void processIncomingFiles(
|
||||
BuildContext context, {
|
||||
required List<SharedMediaFile> sharedFiles,
|
||||
}) async {
|
||||
if (sharedFiles.isEmpty) {
|
||||
return;
|
||||
}
|
||||
Iterable<File> files = sharedFiles.map((file) => File(file.path));
|
||||
if (Platform.isIOS) {
|
||||
files = files
|
||||
.map((file) => File(file.path.replaceAll('file://', '')))
|
||||
.toList();
|
||||
}
|
||||
final supportedFiles = files.where(_isFileTypeSupported);
|
||||
final unsupportedFiles = files.whereNot(_isFileTypeSupported);
|
||||
debugPrint(
|
||||
"Received ${files.length} files, out of which ${supportedFiles.length} are supported.}");
|
||||
if (supportedFiles.isEmpty) {
|
||||
Fluttertoast.showToast(
|
||||
msg: translateError(
|
||||
context,
|
||||
ErrorCode.unsupportedFileFormat,
|
||||
),
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
// As stated in the docs, SystemNavigator.pop() is ignored on IOS to comply with HCI guidelines.
|
||||
await SystemNavigator.pop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (unsupportedFiles.isNotEmpty) {
|
||||
//TODO: INTL
|
||||
Fluttertoast.showToast(
|
||||
msg:
|
||||
"${unsupportedFiles.length}/${files.length} files could not be processed.");
|
||||
}
|
||||
await ShareIntentQueue.instance.addAll(
|
||||
supportedFiles,
|
||||
userId: context.read<LocalUserAccount>().id,
|
||||
);
|
||||
}
|
||||
}
|
||||
105
lib/features/sharing/model/share_intent_queue.dart
Normal file
105
lib/features/sharing/model/share_intent_queue.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ShareIntentQueue extends ChangeNotifier {
|
||||
final Map<String, Queue<File>> _queues = {};
|
||||
|
||||
ShareIntentQueue._();
|
||||
|
||||
static final instance = ShareIntentQueue._();
|
||||
|
||||
Future<void> initialize() async {
|
||||
final users = Hive.localUserAccountBox.values;
|
||||
for (final user in users) {
|
||||
final userId = user.id;
|
||||
debugPrint("Locating remaining files to be uploaded for $userId...");
|
||||
final consumptionDir =
|
||||
await FileService.getConsumptionDirectory(userId: userId);
|
||||
final files = await FileService.getAllFiles(consumptionDir);
|
||||
debugPrint(
|
||||
"Found ${files.length} files to be uploaded for $userId. Adding to queue...");
|
||||
getQueue(userId).addAll(files);
|
||||
}
|
||||
}
|
||||
|
||||
void add(
|
||||
File file, {
|
||||
required String userId,
|
||||
}) =>
|
||||
addAll([file], userId: userId);
|
||||
|
||||
Future<void> addAll(
|
||||
Iterable<File> files, {
|
||||
required String userId,
|
||||
}) async {
|
||||
if (files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final consumptionDirectory =
|
||||
await FileService.getConsumptionDirectory(userId: userId);
|
||||
final copiedFiles = await Future.wait([
|
||||
for (var file in files)
|
||||
file.copy('${consumptionDirectory.path}/${p.basename(file.path)}')
|
||||
]);
|
||||
|
||||
debugPrint(
|
||||
"Adding received files to queue: ${files.map((e) => e.path).join(",")}",
|
||||
);
|
||||
getQueue(userId).addAll(copiedFiles);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Removes and returns the first item in the requested user's queue if it exists.
|
||||
File? pop(String userId) {
|
||||
if (hasUnhandledFiles(userId: userId)) {
|
||||
final file = getQueue(userId).removeFirst();
|
||||
notifyListeners();
|
||||
return file;
|
||||
// Don't notify listeners, only when new item is added.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> onConsumed(File file) {
|
||||
debugPrint(
|
||||
"File ${file.path} successfully consumed. Delelting local copy.");
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
Future<void> discard(File file) {
|
||||
debugPrint("Discarding file ${file.path}.");
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
/// Returns whether the queue of the requested user contains files waiting for processing.
|
||||
bool hasUnhandledFiles({
|
||||
required String userId,
|
||||
}) =>
|
||||
getQueue(userId).isNotEmpty;
|
||||
|
||||
int unhandledFileCount({
|
||||
required String userId,
|
||||
}) =>
|
||||
getQueue(userId).length;
|
||||
|
||||
Queue<File> getQueue(String userId) {
|
||||
if (!_queues.containsKey(userId)) {
|
||||
_queues[userId] = Queue<File>();
|
||||
}
|
||||
return _queues[userId]!;
|
||||
}
|
||||
}
|
||||
|
||||
class UserAwareShareMediaFile {
|
||||
final String userId;
|
||||
final SharedMediaFile sharedFile;
|
||||
|
||||
UserAwareShareMediaFile(this.userId, this.sharedFile);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
class ShareIntentQueue extends ChangeNotifier {
|
||||
final Map<String, Queue<SharedMediaFile>> _queues = {};
|
||||
|
||||
ShareIntentQueue._();
|
||||
|
||||
static final instance = ShareIntentQueue._();
|
||||
|
||||
void add(
|
||||
SharedMediaFile file, {
|
||||
required String userId,
|
||||
}) {
|
||||
debugPrint("Adding received file to queue: ${file.path}");
|
||||
_getQueue(userId).add(file);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addAll(
|
||||
Iterable<SharedMediaFile> files, {
|
||||
required String userId,
|
||||
}) {
|
||||
debugPrint(
|
||||
"Adding received files to queue: ${files.map((e) => e.path).join(",")}");
|
||||
_getQueue(userId).addAll(files);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SharedMediaFile? pop(String userId) {
|
||||
if (userHasUnhandlesFiles(userId)) {
|
||||
return _getQueue(userId).removeFirst();
|
||||
// Don't notify listeners, only when new item is added.
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Queue<SharedMediaFile> _getQueue(String userId) {
|
||||
if (!_queues.containsKey(userId)) {
|
||||
_queues[userId] = Queue<SharedMediaFile>();
|
||||
}
|
||||
return _queues[userId]!;
|
||||
}
|
||||
|
||||
bool userHasUnhandlesFiles(String userId) => _getQueue(userId).isNotEmpty;
|
||||
}
|
||||
|
||||
class UserAwareShareMediaFile {
|
||||
final String userId;
|
||||
final SharedMediaFile sharedFile;
|
||||
|
||||
UserAwareShareMediaFile(this.userId, this.sharedFile);
|
||||
}
|
||||
110
lib/features/sharing/view/consumption_queue_view.dart
Normal file
110
lib/features/sharing/view/consumption_queue_view.dart
Normal file
@@ -0,0 +1,110 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.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/sharing/cubit/receive_share_cubit.dart';
|
||||
import 'package:paperless_mobile/features/sharing/view/widgets/file_thumbnail.dart';
|
||||
import 'package:paperless_mobile/features/sharing/view/widgets/upload_queue_shell.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ConsumptionQueueView extends StatelessWidget {
|
||||
const ConsumptionQueueView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentUser = context.watch<LocalUserAccount>();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Upload Queue"), //TODO: INTL
|
||||
),
|
||||
body: Consumer<ConsumptionChangeNotifier>(
|
||||
builder: (context, value, child) {
|
||||
if (value.pendingFiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text("No pending files."),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final file = value.pendingFiles.elementAt(index);
|
||||
final filename = p.basename(file.path);
|
||||
return ListTile(
|
||||
title: Text(filename),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: FileThumbnail(
|
||||
file: file,
|
||||
fit: BoxFit.cover,
|
||||
width: 75,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ConsumptionChangeNotifier>()
|
||||
.discardFile(file, userId: currentUser.id);
|
||||
},
|
||||
),
|
||||
);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(filename, maxLines: 1),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
ActionChip(
|
||||
label: Text(S.of(context)!.upload),
|
||||
avatar: Icon(Icons.file_upload_outlined),
|
||||
onPressed: () {
|
||||
consumeLocalFile(
|
||||
context,
|
||||
file: file,
|
||||
userId: currentUser.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: Text(S.of(context)!.discard),
|
||||
avatar: Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ConsumptionChangeNotifier>()
|
||||
.discardFile(
|
||||
file,
|
||||
userId: currentUser.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
),
|
||||
],
|
||||
).padded();
|
||||
},
|
||||
itemCount: value.pendingFiles.length,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.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/future_or_builder.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:transparent_image/transparent_image.dart';
|
||||
|
||||
class DiscardSharedFileDialog extends StatelessWidget {
|
||||
final FutureOr<Uint8List> bytes;
|
||||
const DiscardSharedFileDialog({
|
||||
super.key,
|
||||
required this.bytes,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
icon: FutureOrBuilder<Uint8List>(
|
||||
future: bytes,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
return LimitedBox(
|
||||
maxHeight: 200,
|
||||
maxWidth: 200,
|
||||
child: FadeInImage(
|
||||
fit: BoxFit.contain,
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
image: MemoryImage(snapshot.data!),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: Text(S.of(context)!.discardFile),
|
||||
content: Text(
|
||||
"The shared file was not yet processed. Do you want to discrad the file?", //TODO: INTL
|
||||
),
|
||||
actions: [
|
||||
DialogCancelButton(),
|
||||
DialogConfirmButton(
|
||||
label: S.of(context)!.discard,
|
||||
style: DialogConfirmButtonStyle.danger,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
102
lib/features/sharing/view/widgets/file_thumbnail.dart
Normal file
102
lib/features/sharing/view/widgets/file_thumbnail.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mime/mime.dart' as mime;
|
||||
import 'package:printing/printing.dart';
|
||||
import 'package:transparent_image/transparent_image.dart';
|
||||
|
||||
class FileThumbnail extends StatefulWidget {
|
||||
final File? file;
|
||||
final Uint8List? bytes;
|
||||
|
||||
final BoxFit? fit;
|
||||
final double? width;
|
||||
final double? height;
|
||||
const FileThumbnail({
|
||||
super.key,
|
||||
this.file,
|
||||
this.bytes,
|
||||
this.fit,
|
||||
this.width,
|
||||
this.height,
|
||||
}) : assert((bytes != null) != (file != null));
|
||||
|
||||
@override
|
||||
State<FileThumbnail> createState() => _FileThumbnailState();
|
||||
}
|
||||
|
||||
class _FileThumbnailState extends State<FileThumbnail> {
|
||||
late String? mimeType;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mimeType = widget.file != null
|
||||
? mime.lookupMimeType(widget.file!.path)
|
||||
: mime.lookupMimeType('', headerBytes: widget.bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (mimeType) {
|
||||
"application/pdf" => SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Center(
|
||||
child: FutureBuilder<Uint8List?>(
|
||||
future: widget.file?.readAsBytes().then(_convertPdfToPng) ??
|
||||
_convertPdfToPng(widget.bytes!),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Image.memory(
|
||||
snapshot.data!,
|
||||
alignment: Alignment.topCenter,
|
||||
fit: widget.fit,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
"image/png" ||
|
||||
"image/jpeg" ||
|
||||
"image/tiff" ||
|
||||
"image/gif" ||
|
||||
"image/webp" =>
|
||||
widget.file != null
|
||||
? Image.file(
|
||||
widget.file!,
|
||||
fit: widget.fit,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
)
|
||||
: Image.memory(
|
||||
widget.bytes!,
|
||||
fit: widget.fit,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
),
|
||||
"text/plain" => const Center(
|
||||
child: Text(".txt"),
|
||||
),
|
||||
_ => const Icon(Icons.file_present_outlined),
|
||||
};
|
||||
}
|
||||
|
||||
// send pdfFile as params
|
||||
Future<Uint8List?> _convertPdfToPng(Uint8List bytes) async {
|
||||
final info = await Printing.info();
|
||||
if (!info.canRaster) {
|
||||
return kTransparentImage;
|
||||
}
|
||||
final raster = await Printing.raster(bytes, pages: [0], dpi: 72).first;
|
||||
return raster.toPng();
|
||||
}
|
||||
}
|
||||
195
lib/features/sharing/view/widgets/upload_queue_shell.dart
Normal file
195
lib/features/sharing/view/widgets/upload_queue_shell.dart
Normal file
@@ -0,0 +1,195 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.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/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:paperless_mobile/features/sharing/cubit/receive_share_cubit.dart';
|
||||
import 'package:paperless_mobile/features/sharing/view/dialog/discard_shared_file_dialog.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/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/upload_queue_route.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
class UploadQueueShell extends StatefulWidget {
|
||||
final Widget child;
|
||||
const UploadQueueShell({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<UploadQueueShell> createState() => _UploadQueueShellState();
|
||||
}
|
||||
|
||||
class _UploadQueueShellState extends State<UploadQueueShell> {
|
||||
StreamSubscription? _subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ReceiveSharingIntent.getInitialMedia().then(_onReceiveSharedFiles);
|
||||
_subscription =
|
||||
ReceiveSharingIntent.getMediaStream().listen(_onReceiveSharedFiles);
|
||||
|
||||
// WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
// context.read<ReceiveShareCubit>().loadFromConsumptionDirectory(
|
||||
// userId: context.read<LocalUserAccount>().id,
|
||||
// );
|
||||
// final state = context.read<ReceiveShareCubit>().state;
|
||||
// print("Current state is " + state.toString());
|
||||
// final files = state.files;
|
||||
// if (files.isNotEmpty) {
|
||||
// showSnackBar(
|
||||
// context,
|
||||
// "You have ${files.length} shared files waiting to be uploaded.",
|
||||
// action: SnackBarActionConfig(
|
||||
// label: "Show me",
|
||||
// onPressed: () {
|
||||
// UploadQueueRoute().push(context);
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// // showDialog(
|
||||
// // context: context,
|
||||
// // builder: (context) => AlertDialog(
|
||||
// // title: Text("Pending files"),
|
||||
// // content: Text(
|
||||
// // "You have ${files.length} files waiting to be uploaded.",
|
||||
// // ),
|
||||
// // actions: [
|
||||
// // TextButton(
|
||||
// // child: Text(S.of(context)!.gotIt),
|
||||
// // onPressed: () {
|
||||
// // Navigator.pop(context);
|
||||
// // UploadQueueRoute().push(context);
|
||||
// // },
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // );
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
context.read<PendingTasksNotifier>().addListener(_onTasksChanged);
|
||||
}
|
||||
|
||||
void _onTasksChanged() {
|
||||
final taskNotifier = context.read<PendingTasksNotifier>();
|
||||
for (var task in taskNotifier.value.values) {
|
||||
context.read<LocalNotificationService>().notifyTaskChanged(task);
|
||||
}
|
||||
}
|
||||
|
||||
void _onReceiveSharedFiles(List<SharedMediaFile> sharedFiles) async {
|
||||
final files = sharedFiles.map((file) => File(file.path)).toList();
|
||||
|
||||
if (files.isNotEmpty) {
|
||||
final userId = context.read<LocalUserAccount>().id;
|
||||
final notifier = context.read<ConsumptionChangeNotifier>();
|
||||
await notifier.addFiles(
|
||||
files: files,
|
||||
userId: userId,
|
||||
);
|
||||
final localFiles = notifier.pendingFiles;
|
||||
for (int i = 0; i < localFiles.length; i++) {
|
||||
final file = localFiles[i];
|
||||
await consumeLocalFile(
|
||||
context,
|
||||
file: file,
|
||||
userId: userId,
|
||||
exitAppAfterConsumed: i == localFiles.length - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
context.read<PendingTasksNotifier>().removeListener(_onTasksChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> consumeLocalFile(
|
||||
BuildContext context, {
|
||||
required File file,
|
||||
required String userId,
|
||||
bool exitAppAfterConsumed = false,
|
||||
}) async {
|
||||
final consumptionNotifier = context.read<ConsumptionChangeNotifier>();
|
||||
final taskNotifier = context.read<PendingTasksNotifier>();
|
||||
final ioFile = File(file.path);
|
||||
// if (!await ioFile.exists()) {
|
||||
// Fluttertoast.showToast(
|
||||
// msg: S.of(context)!.couldNotAccessReceivedFile,
|
||||
// toastLength: Toast.LENGTH_LONG,
|
||||
// );
|
||||
// }
|
||||
|
||||
final bytes = ioFile.readAsBytes();
|
||||
final shouldDirectlyUpload =
|
||||
Hive.globalSettingsBox.getValue()!.skipDocumentPreprarationOnUpload;
|
||||
if (shouldDirectlyUpload) {
|
||||
final taskId = await context.read<PaperlessDocumentsApi>().create(
|
||||
await bytes,
|
||||
filename: p.basename(file.path),
|
||||
title: p.basenameWithoutExtension(file.path),
|
||||
);
|
||||
consumptionNotifier.discardFile(file, userId: userId);
|
||||
if (taskId != null) {
|
||||
taskNotifier.listenToTaskChanges(taskId);
|
||||
}
|
||||
} else {
|
||||
final result = await DocumentUploadRoute(
|
||||
$extra: bytes,
|
||||
filename: p.basenameWithoutExtension(file.path),
|
||||
title: p.basenameWithoutExtension(file.path),
|
||||
fileExtension: p.extension(file.path),
|
||||
).push<DocumentUploadResult>(context) ??
|
||||
DocumentUploadResult(false, null);
|
||||
|
||||
if (result.success) {
|
||||
await Fluttertoast.showToast(
|
||||
msg: S.of(context)!.documentSuccessfullyUploadedProcessing,
|
||||
);
|
||||
await consumptionNotifier.discardFile(file, userId: userId);
|
||||
|
||||
if (result.taskId != null) {
|
||||
taskNotifier.listenToTaskChanges(result.taskId!);
|
||||
}
|
||||
if (exitAppAfterConsumed) {
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
} else {
|
||||
final shouldDiscard = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => DiscardSharedFileDialog(bytes: bytes),
|
||||
) ??
|
||||
false;
|
||||
if (shouldDiscard) {
|
||||
await context
|
||||
.read<ConsumptionChangeNotifier>()
|
||||
.discardFile(file, userId: userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user