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 on State { 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 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); } }