Files
Client/lib/widget/downloads/download_trigger.dart
T

101 lines
3.1 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import '../../share_intent/remote_file_ref.dart';
import '../../utils/downloads/download_job.dart';
import '../../utils/downloads/download_manager.dart';
import '../confirm_dialog.dart';
/// Shared download trigger logic for the Files list and Talk chat bubbles.
///
/// Owns attaching to / detaching from a [DownloadJob] and starting/cancelling a
/// download. Rendering of inline progress stays with each screen (list subtitle
/// vs. bubble overlay) via [downloadJob]. Opening the finished file, the
/// completion/parallel behaviour and error surfacing are handled centrally by
/// the downloads tray/coordinator — not here.
mixin DownloadTrigger<T extends StatefulWidget> on State<T> {
DownloadJob? _job;
/// Current job for inline rendering, or null when idle.
DownloadJob? get downloadJob => _job;
/// Remote (WebDAV) path of the file this widget can download, or null when
/// the widget currently has no downloadable file (e.g. a text message).
String? get downloadRemotePath;
/// True while a download for this widget's file is running.
bool get isDownloading => _job?.status.value is DownloadInProgress;
/// Reattach to an in-flight job on mount. Call from `initState`.
void initDownloadTrigger() => _attach(_lookup());
/// Re-resolve the job after the widget's file changed. Call from
/// `didUpdateWidget` when [downloadRemotePath] may have changed.
void refreshDownloadTrigger() {
_detach();
_attach(_lookup());
}
/// Detach listeners. Call from `dispose`.
void disposeDownloadTrigger() => _detach();
DownloadJob? _lookup() {
final path = downloadRemotePath;
return path == null ? null : DownloadManager.instance.jobFor(path);
}
void _attach(DownloadJob? job) {
_job = job;
job?.status.addListener(_onStatus);
}
void _detach() {
_job?.status.removeListener(_onStatus);
_job = null;
}
void _onStatus() {
if (!mounted) return;
final job = _job;
setState(() {});
// Once finished, the tray owns the job (open / retry / dismiss); drop our
// reference so the inline progress clears and we never touch a job the
// manager is about to dispose.
if (job != null && job.isFinished) _detach();
}
/// Starts (or reattaches to) the download for this widget's file.
Future<void> startDownload({
required String name,
RemoteFileRef? remoteFile,
}) async {
final path = downloadRemotePath;
if (path == null) return;
final job = await DownloadManager.instance.start(
remotePath: path,
name: name,
remoteFile: remoteFile,
);
if (!mounted) return;
if (_job != job) {
_detach();
_attach(job);
}
setState(() {});
}
/// Confirms and cancels the in-flight download.
void confirmCancelDownload() {
final job = _job;
if (job == null || job.isFinished) return;
ConfirmDialog(
title: 'Download abbrechen?',
content: 'Möchtest du den Download abbrechen?',
confirmButton: 'Ja, Abbrechen',
cancelButton: 'Nein',
onConfirm: () => unawaited(DownloadManager.instance.cancel(job)),
).asDialog(context);
}
}