fix: починил загрузку файлов

This commit is contained in:
Jganenokk
2026-07-23 17:49:56 +07:00
parent 476951e894
commit 5a3e1c8883
5 changed files with 95 additions and 40 deletions
+3 -2
View File
@@ -1786,9 +1786,10 @@ class MessagesModule {
}) async { }) async {
try { try {
final response = await _api.sendRequest(Opcode.fileDownload, { final response = await _api.sendRequest(Opcode.fileDownload, {
'messageId': int.tryParse(messageId) ?? 0,
'chatId': chatId,
'fileId': fileId, 'fileId': fileId,
'chatId': chatId,
'messageId': int.tryParse(messageId) ?? 0,
'itemType': 'REGULAR',
}); });
if (!response.isOk) return null; if (!response.isOk) return null;
+34
View File
@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -18,6 +19,8 @@ class MediaCache {
static Directory? _dir; static Directory? _dir;
static int? _cachedSize; static int? _cachedSize;
static final Map<String, Future<File?>> _inFlight = {}; static final Map<String, Future<File?>> _inFlight = {};
static final Set<String> _present = {};
static final Map<String, ValueNotifier<bool>> _presence = {};
static Future<Directory> _cacheDir() async { static Future<Directory> _cacheDir() async {
final cached = _dir; final cached = _dir;
@@ -49,8 +52,10 @@ class MediaCache {
try { try {
await file.setLastModified(DateTime.now()); await file.setLastModified(DateTime.now());
} catch (_) {} } catch (_) {}
_markPresent(name, true);
return file; return file;
} }
_markPresent(name, false);
return null; return null;
} }
@@ -103,6 +108,7 @@ class MediaCache {
} }
await sink.close(); await sink.close();
await part.rename(file.path); await part.rename(file.path);
_markPresent(name, true);
final known = _cachedSize; final known = _cachedSize;
if (known != null) { if (known != null) {
try { try {
@@ -161,6 +167,10 @@ class MediaCache {
} }
} }
_cachedSize = 0; _cachedSize = 0;
_present.clear();
for (final notifier in _presence.values) {
notifier.value = false;
}
return freed; return freed;
} }
@@ -195,11 +205,35 @@ class MediaCache {
try { try {
total -= await file.length(); total -= await file.length();
await file.delete(); await file.delete();
_markAbsentByBasename(p.basename(file.path));
} catch (_) {} } catch (_) {}
} }
_cachedSize = total; _cachedSize = total;
} }
/// Реактивный флаг наличия файла [name] в кэше (для UI-иконки «скачано»).
static ValueListenable<bool> presence(String name) {
final key = _sanitize(name);
return _presence.putIfAbsent(key, () {
final notifier = ValueNotifier(_present.contains(key));
if (!notifier.value) existing(name).ignore();
return notifier;
});
}
static void _markPresent(String name, bool present) {
final key = _sanitize(name);
final changed = present ? _present.add(key) : _present.remove(key);
if (!changed) return;
_presence[key]?.value = present;
}
static void _markAbsentByBasename(String basename) {
if (_present.remove(basename)) {
_presence[basename]?.value = false;
}
}
static String _sanitize(String name) { static String _sanitize(String name) {
final cleaned = name.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim(); final cleaned = name.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
return cleaned.isEmpty ? 'file' : cleaned; return cleaned.isEmpty ? 'file' : cleaned;
@@ -53,6 +53,7 @@ class ComposerInputBar extends StatelessWidget {
this.showAttachButton = true, this.showAttachButton = true,
this.forceSend = false, this.forceSend = false,
this.hintText = 'Message', this.hintText = 'Message',
this.bottomSafe = true,
}); });
final String chatType; final String chatType;
@@ -87,6 +88,7 @@ class ComposerInputBar extends StatelessWidget {
final bool showAttachButton; final bool showAttachButton;
final bool forceSend; final bool forceSend;
final String hintText; final String hintText;
final bool bottomSafe;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -172,6 +174,7 @@ class ComposerInputBar extends StatelessWidget {
} }
final bar = SafeArea( final bar = SafeArea(
bottom: bottomSafe,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
+9 -5
View File
@@ -2353,9 +2353,12 @@ class _ChatScreenState extends State<ChatScreen>
); );
}, },
), ),
ComposerInputBar( AnimatedBuilder(
chatType: _commentsMode ? 'CHAT' : widget.chatType, animation: _stickers.anim,
chrome: _effectiveChrome, builder: (context, _) => ComposerInputBar(
bottomSafe: _stickers.anim.value == 0,
chatType: _commentsMode ? 'CHAT' : widget.chatType,
chrome: _effectiveChrome,
style: AppComposerStyle.current.value, style: AppComposerStyle.current.value,
background: AppComposerBackground.current.value, background: AppComposerBackground.current.value,
backdropKey: _pillBackdrop, backdropKey: _pillBackdrop,
@@ -2385,8 +2388,9 @@ class _ChatScreenState extends State<ChatScreen>
onSubscribe: _subscribeChannel, onSubscribe: _subscribeChannel,
showStickerButton: !_commentsMode, showStickerButton: !_commentsMode,
showAttachButton: !_commentsMode, showAttachButton: !_commentsMode,
forceSend: _commentsMode, forceSend: _commentsMode,
hintText: _commentsMode ? 'Комментарий' : 'Message', hintText: _commentsMode ? 'Комментарий' : 'Message',
),
), ),
StickerPanelView( StickerPanelView(
stickers: _stickers, stickers: _stickers,
@@ -5,6 +5,7 @@ import 'package:komet/main.dart';
import '../../../../core/utils/download_progress.dart'; import '../../../../core/utils/download_progress.dart';
import '../../../../core/utils/file_download.dart'; import '../../../../core/utils/file_download.dart';
import '../../../../core/utils/media_cache.dart';
import '../../../../core/utils/format.dart'; import '../../../../core/utils/format.dart';
import '../../../../core/utils/haptics.dart'; import '../../../../core/utils/haptics.dart';
import '../../../../models/attachment.dart'; import '../../../../models/attachment.dart';
@@ -113,38 +114,49 @@ class FileBubble extends StatelessWidget {
ValueListenableBuilder<double?>( ValueListenableBuilder<double?>(
valueListenable: MediaDownloadProgress.notifier(cacheName), valueListenable: MediaDownloadProgress.notifier(cacheName),
builder: (context, progress, _) { builder: (context, progress, _) {
final downloading = progress != null; final iconColor = isMe
return GestureDetector( ? ctx.cs.onPrimaryContainer
onTap: downloading : ctx.cs.primary;
? null Widget circle(Widget child, VoidCallback? onTap) {
: () => _downloadFile(ctx.context, file, name), return GestureDetector(
child: Container( onTap: onTap,
width: 34, child: Container(
height: 34, width: 34,
decoration: BoxDecoration( height: 34,
color: isMe decoration: BoxDecoration(
? ctx.systemTint color: isMe
: ctx.cs.surfaceContainerHighest, ? ctx.systemTint
shape: BoxShape.circle, : ctx.cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: child,
), ),
child: downloading );
? Padding( }
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator( if (progress != null) {
strokeWidth: 2, return circle(
value: progress > 0 ? progress : null, Padding(
color: isMe padding: const EdgeInsets.all(8),
? ctx.cs.onPrimaryContainer child: CircularProgressIndicator(
: ctx.cs.primary, strokeWidth: 2,
), value: progress > 0 ? progress : null,
) color: iconColor,
: Icon( ),
Symbols.download, ),
color: isMe null,
? ctx.cs.onPrimaryContainer );
: ctx.cs.primary, }
size: 18,
), return ValueListenableBuilder<bool>(
valueListenable: MediaCache.presence(cacheName),
builder: (context, cached, _) => circle(
Icon(
cached ? Symbols.check : Symbols.download,
color: iconColor,
size: 18,
),
() => _downloadFile(ctx.context, file, name),
), ),
); );
}, },
@@ -171,8 +183,9 @@ class FileBubble extends StatelessWidget {
Haptics.tap(); Haptics.tap();
final cacheName = '${fileId}_$name'; final cacheName = '${fileId}_$name';
final cached = (await MediaCache.existing(cacheName)) != null;
MediaDownloadProgress.set(cacheName, 0); if (!cached) MediaDownloadProgress.set(cacheName, 0);
final result = await openCachedFile( final result = await openCachedFile(
cacheName, cacheName,
() => messagesModule.getFileUrl( () => messagesModule.getFileUrl(
@@ -182,7 +195,7 @@ class FileBubble extends StatelessWidget {
), ),
onProgress: (p) => MediaDownloadProgress.set(cacheName, p), onProgress: (p) => MediaDownloadProgress.set(cacheName, p),
); );
MediaDownloadProgress.set(cacheName, null); if (!cached) MediaDownloadProgress.set(cacheName, null);
if (!context.mounted) return; if (!context.mounted) return;
if (!result.ok) { if (!result.ok) {
showCustomNotification( showCustomNotification(