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