feature: media auto-download + manual download + UI download state indication (Android + desktop)

C-layer:
- chat_event.h: CHAT_EVT_ATTACHMENT_DOWNLOADED (14)
- chat_msg.c: fixed md_auto_download (non-NULL done_cb, correct media_base from db_path, use chat_setting for autoload checks)
- chat_msg.c: added chat_core_attachment_download + trampoline for manual download
- chat_core.h: exported attachment_dl_req + trampoline

Android (Kotlin + JNI):
- Message: downloadState field (parse from localAttrs)
- ChatRepository: parse localAttrs for download state
- NativeLib: attachmentDownload() JNI
- MessageBubble: FileBubble with download status icon + tap-to-download
- ChatViewModel: downloadAttachment() + CHAT_EVT_ATTACHMENT_DOWNLOADED handler
- ChatScreen: wire onDownloadFile handler
- android_jni_bridge: utun_bridge_attachment_download + JNI + localAttrs in JSON

Desktop (Qt/C++):
- messagedelegate.h: MsgFileDownloadStateRole, MsgChannelIdRole
- messagelist.cpp: parse localAttrs for file download state
- messagedelegate.cpp: FileBubble with download icon/text + click handler via chat_core_attachment_download_trampoline
- gui_bridge.h: GUI_EVT_ATTACHMENT_DOWNLOADED (14) + callback typedef
- gui_bridge_impl.cpp: event dispatch + setter
- mainwindow.cpp: callback registration -> refresh message list
This commit is contained in:
evgeny committed 2026-07-30 00:01:38 +03:00
1 parent b8920cf483
commit 70797f921e
23 files changed
+558 -174

No files matched your search

+39 -11
View File
@@ -20,6 +20,14 @@
#include <QFile>
#include <QDebug>
extern "C" {
#include "../transport/gui_bridge.h"
#include "chat/chat_core.h"
#include "../../lib/u_async.h"
#include "../../lib/mem.h"
void chat_core_attachment_download_trampoline(void* arg);
}
MessageDelegate::MessageDelegate(QObject *parent)
: QStyledItemDelegate(parent) {}
@@ -67,6 +75,24 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QModelIndex &index) {
if (event->type() != QEvent::MouseButtonRelease) return false;
Layout L = calcLayout(option, index);
if (L.isFile) {
int dlState = index.data(MsgFileDownloadStateRole).toInt();
if (dlState != 0) return false; /* already downloaded or in progress */
qint64 msgId = index.data(MsgIdRole).toLongLong();
QString chId = index.data(MsgChannelIdRole).toString();
if (chId.isEmpty() || msgId <= 0) return false;
struct attachment_dl_req* req = (struct attachment_dl_req*)
u_malloc(sizeof(struct attachment_dl_req));
if (!req) return false;
memset(req, 0, sizeof(*req));
strncpy(req->channel_id, chId.toUtf8().constData(), sizeof(req->channel_id) - 1);
req->msg_id = msgId;
gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req);
return true;
}
if (!L.isVoice) return false;
QMouseEvent* me = static_cast<QMouseEvent*>(event);
@@ -797,17 +823,22 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
return;
}
if (L.isFile) {
if (L.isFile) {
drawBubble(painter, L, option);
if (!L.narrow) drawAvatar(painter, L, index);
/* File icon */
int dlState = index.data(MsgFileDownloadStateRole).toInt();
/* File icon based on download state: 0=not_started, 1=downloading, 2=downloaded */
QFont iconFont = option.font;
iconFont.setPointSize(iconFont.pointSize() + 6);
painter->setFont(iconFont);
painter->setPen(option.palette.text().color());
QRect iconRect(L.bubbleRect.left() + kPadH, kPadTop, 24, 24);
painter->drawText(iconRect, Qt::AlignCenter, QString::fromUtf8("\xF0\x9F\x93\x84"));
if (dlState == 2)
painter->drawText(iconRect, Qt::AlignCenter, QString::fromUtf8("\xE2\x9C\x85"));
else
painter->drawText(iconRect, Qt::AlignCenter, QString::fromUtf8("\xE2\xAC\x87\xEF\xB8\x8F"));
QFont fileFont = option.font;
fileFont.setBold(true);
@@ -822,19 +853,16 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
painter->setPen(QColor(0x80, 0x80, 0x80));
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
QString sizeStr = formatFileSize(fsz);
QString stateStr = (dlState == 2) ? "Downloaded" : "Tap to download";
QString info = sizeStr + " \xC2\xB7 " + stateStr;
int nameH = QFontMetrics(fileFont).height();
QRect sizeRect(L.textRect.left(), L.textRect.top() + nameH, L.textRect.width(), QFontMetrics(sizeFont).height());
painter->drawText(sizeRect, Qt::AlignLeft | Qt::AlignVCenter, sizeStr);
/* Timestamp */
QFont tsFont = option.font;
tsFont.setPointSize(tsFont.pointSize() - 1);
painter->setFont(tsFont);
painter->drawText(QRect(L.textRect.right() - 40, L.textRect.top() + nameH, 40, QFontMetrics(sizeFont).height()),
Qt::AlignRight | Qt::AlignVCenter, index.data(MsgTimeRole).toString());
painter->drawText(sizeRect, Qt::AlignLeft | Qt::AlignVCenter, info);
painter->restore();
return;
}
auto *cv = qobject_cast<const ChatView *>(option.widget);