added support for simultan downloads in files and talk, support for background downloads, enhanced loading in files with retry
This commit is contained in:
@@ -5,13 +5,11 @@ import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
|
||||
import '../../../../extensions/date_time.dart';
|
||||
import '../../../../extensions/text.dart';
|
||||
import '../../../../routing/app_routes.dart';
|
||||
import '../../../../share_intent/remote_file_ref.dart';
|
||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../utils/download_manager.dart';
|
||||
import '../../../../utils/downloads/download_job.dart';
|
||||
import '../../../../utils/haptics.dart';
|
||||
import '../../../../widget/confirm_dialog.dart';
|
||||
import '../../../../widget/info_dialog.dart';
|
||||
import '../../../../widget/downloads/download_trigger.dart';
|
||||
import '../data/chat_bubble_styles.dart';
|
||||
import '../data/chat_message.dart';
|
||||
import 'answer_reference.dart';
|
||||
@@ -58,97 +56,40 @@ class ChatBubble extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChatBubbleState extends State<ChatBubble>
|
||||
with SingleTickerProviderStateMixin {
|
||||
with SingleTickerProviderStateMixin, DownloadTrigger<ChatBubble> {
|
||||
late ChatMessage message;
|
||||
DownloadJob? _job;
|
||||
|
||||
Offset _position = Offset.zero;
|
||||
Offset _dragStartPosition = Offset.zero;
|
||||
bool _swipeActionArmed = false;
|
||||
|
||||
@override
|
||||
String? get downloadRemotePath =>
|
||||
widget.bubbleData.messageParameters?['file']?.path;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final filePath = widget.bubbleData.messageParameters?['file']?.path;
|
||||
if (filePath != null) _attachJob(DownloadManager.instance.jobFor(filePath));
|
||||
initDownloadTrigger();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ChatBubble oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
final oldPath = oldWidget.bubbleData.messageParameters?['file']?.path;
|
||||
if (oldPath != downloadRemotePath) refreshDownloadTrigger();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_detachJob();
|
||||
disposeDownloadTrigger();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _attachJob(DownloadJob? job) {
|
||||
_job = job;
|
||||
if (job == null) return;
|
||||
job.status.addListener(_onStatusChange);
|
||||
if (job.isFinished) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _onStatusChange());
|
||||
}
|
||||
}
|
||||
|
||||
void _detachJob() {
|
||||
_job?.status.removeListener(_onStatusChange);
|
||||
_job = null;
|
||||
}
|
||||
|
||||
void _onStatusChange() {
|
||||
if (!mounted) return;
|
||||
final job = _job;
|
||||
if (job == null) return;
|
||||
final status = job.status.value;
|
||||
if (status is DownloadDone) {
|
||||
Haptics.success();
|
||||
DownloadManager.instance.clear(job.remotePath);
|
||||
_detachJob();
|
||||
final talkFile = message.file;
|
||||
AppRoutes.openFileViewer(
|
||||
context,
|
||||
status.localPath,
|
||||
remoteFile: talkFile != null
|
||||
? RemoteFileRef.fromTalk(talkFile)
|
||||
: null,
|
||||
);
|
||||
setState(() {});
|
||||
} else if (status is DownloadFailed) {
|
||||
final message = status.message;
|
||||
DownloadManager.instance.clear(job.remotePath);
|
||||
_detachJob();
|
||||
setState(() {});
|
||||
InfoDialog.show(context, message, title: 'Download fehlgeschlagen');
|
||||
} else if (status is DownloadCancelled) {
|
||||
DownloadManager.instance.clear(job.remotePath);
|
||||
_detachJob();
|
||||
setState(() {});
|
||||
} else {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startFileDownload() async {
|
||||
void _startFileDownload() {
|
||||
final file = message.file;
|
||||
final filePath = file?.path;
|
||||
if (file == null || filePath == null) return;
|
||||
final job = await DownloadManager.instance.start(
|
||||
remotePath: filePath,
|
||||
name: file.name,
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (_job == job) return;
|
||||
_detachJob();
|
||||
_attachJob(job);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _confirmCancel() {
|
||||
ConfirmDialog(
|
||||
title: 'Download abbrechen?',
|
||||
content: 'Möchtest du den Download abbrechen?',
|
||||
confirmButton: 'Ja, Abbrechen',
|
||||
cancelButton: 'Nein',
|
||||
onConfirm: () => _job?.cancel(),
|
||||
).asDialog(context);
|
||||
if (file == null) return;
|
||||
startDownload(name: file.name, remoteFile: RemoteFileRef.fromTalk(file));
|
||||
}
|
||||
|
||||
bool get _rendersAsCommentBubble =>
|
||||
@@ -236,8 +177,8 @@ class _ChatBubbleState extends State<ChatBubble>
|
||||
return;
|
||||
}
|
||||
if (message.file == null) return;
|
||||
if (_job?.status.value is DownloadInProgress) {
|
||||
_confirmCancel();
|
||||
if (isDownloading) {
|
||||
confirmCancelDownload();
|
||||
} else {
|
||||
_startFileDownload();
|
||||
}
|
||||
@@ -349,7 +290,7 @@ class _ChatBubbleState extends State<ChatBubble>
|
||||
timeIconColor: widget.timeIconColor,
|
||||
showActorDisplayName: showActorDisplayName,
|
||||
showBubbleTime: showBubbleTime,
|
||||
downloadJob: _job,
|
||||
downloadJob: downloadJob,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../../../../widget/async_action_button.dart';
|
||||
import '../../../../widget/details_bottom_sheet.dart';
|
||||
import '../../../../widget/downloads/download_tray.dart';
|
||||
import '../../../../widget/emoji_picker_dialog.dart';
|
||||
import '../../../../widget/file_pick.dart';
|
||||
import '../../../../widget/focus_behaviour.dart';
|
||||
@@ -34,8 +35,17 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
final TextEditingController _textBoxController = TextEditingController();
|
||||
final AsyncActionController _sendController = AsyncActionController();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
final GlobalKey _sizeKey = GlobalKey();
|
||||
String? _sendError;
|
||||
|
||||
/// Publishes the input bar's measured height so the downloads chip floats
|
||||
/// above it instead of covering it.
|
||||
void _publishHeight() {
|
||||
if (!mounted) return;
|
||||
final height = _sizeKey.currentContext?.size?.height;
|
||||
if (height != null) downloadChipBottomObstruction.value = height;
|
||||
}
|
||||
|
||||
void share(List<String> uploadedRemotePaths) {
|
||||
shareFilesToChat(
|
||||
token: widget.sendToToken,
|
||||
@@ -104,6 +114,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
downloadChipBottomObstruction.value = 0;
|
||||
_sendController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
@@ -236,7 +247,9 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
}
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _publishHeight());
|
||||
return Stack(
|
||||
key: _sizeKey,
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
|
||||
Reference in New Issue
Block a user