101 lines
3.1 KiB
Dart
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);
|
|
}
|
|
}
|