chatgui/chatgui-android: просмотр изображений с зумом + инфо о медиа
- chatgui: просмотрщик изображений (ImageViewerWindow) с зумом/поворотом - chatgui: отправка картинок как image с определением разрешения и типа - chatgui: у видео-пузыря строка info (res·size·bitrate) и бейдж только длительность - chatgui: контекстное меню медиа (сохранить в файл/скачать) - chatgui-android: большие emoji-only сообщения, битрейт/разрешение у видео - chatgui-android: сохранение неактивных интерфейсов как закомментированные секции - chatgui-android: отправка картинок по расширению (jpg/png/gif/bmp/webp) - photo_sender: content-type и расширение по типу файла
This commit is contained in:
1 parent
ca16b46404
commit
c474c37284
17 files changed
+854
-66
No files matched your search
@@ -23,6 +23,9 @@
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QImageReader>
|
||||
#include <QMenu>
|
||||
#include <QFileDialog>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
@@ -220,10 +223,46 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
||||
QString d = QString("%1:%2").arg(s / 60).arg(s % 60, 2, 10, QChar('0'));
|
||||
item->setData(d, MsgVoiceDurationRole);
|
||||
}
|
||||
item->setData((qlonglong)durMs, MsgVideoDurationMsRole);
|
||||
} else if (p.startsWith("w=")) {
|
||||
item->setData(p.mid(2).toInt(), MsgVideoWidthRole);
|
||||
} else if (p.startsWith("h=")) {
|
||||
item->setData(p.mid(2).toInt(), MsgVideoHeightRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject la = QJsonDocument::fromJson(localAttrs).object();
|
||||
QString st = la.value("st").toString();
|
||||
QString fp = la.value("fp").toString();
|
||||
int dlState = 0;
|
||||
if (st == "fl" && !fp.isEmpty()) {
|
||||
dlState = 2;
|
||||
QString fullPath = mediaDirBase + "/" + channelId + "/" + fp;
|
||||
item->setData(fullPath, MsgVoiceFileRole);
|
||||
}
|
||||
item->setData(dlState, MsgFileDownloadStateRole);
|
||||
}
|
||||
if (ct.startsWith("image/")) {
|
||||
QString raw = QString::fromUtf8(body);
|
||||
int pipePos = raw.indexOf('|');
|
||||
QString fname = (pipePos > 0) ? raw.left(pipePos) : raw;
|
||||
item->setData(fname, MsgFileDisplayNameRole);
|
||||
|
||||
if (pipePos > 0) {
|
||||
QString meta = raw.mid(pipePos + 1);
|
||||
QStringList parts = meta.split('|');
|
||||
if (parts.size() >= 1)
|
||||
item->setData(parts[0].toULongLong(), MsgFileSizeRole);
|
||||
if (parts.size() >= 3)
|
||||
item->setData(parts[2].toUInt(), MsgMediaNumBlocksRole);
|
||||
/* хвост: |w=<w>|h=<h> */
|
||||
for (const QString& p : parts) {
|
||||
if (p.startsWith("w=")) item->setData(p.mid(2).toInt(), MsgImageWidthRole);
|
||||
else if (p.startsWith("h=")) item->setData(p.mid(2).toInt(), MsgImageHeightRole);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject la = QJsonDocument::fromJson(localAttrs).object();
|
||||
QString st = la.value("st").toString();
|
||||
QString fp = la.value("fp").toString();
|
||||
@@ -274,9 +313,15 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
msgDelegate->setVideoOpenCallback([this](const QString& path) {
|
||||
emit openVideoRequested(path);
|
||||
});
|
||||
msgDelegate->setImageOpenCallback([this](const QString& path) {
|
||||
emit openImageRequested(path);
|
||||
});
|
||||
m_view->setItemDelegate(msgDelegate);
|
||||
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_view->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_view, &QWidget::customContextMenuRequested,
|
||||
this, &MessageList::onMessageContextMenu);
|
||||
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
m_view->setFrameShape(QFrame::NoFrame);
|
||||
@@ -399,6 +444,48 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
gui_bridge_post_uasync_fn(chat_core_submit_trampoline, req);
|
||||
});
|
||||
|
||||
connect(m_inputBar, &InputBar::sendImageAttachment, this, [this](const QString& mediaSrc, const QString& mediaDest,
|
||||
const QString& displayName) {
|
||||
if (m_currentChannelId.isEmpty()) return;
|
||||
|
||||
QByteArray textData = displayName.toUtf8();
|
||||
auto ts = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
QFileInfo fi(displayName);
|
||||
QString suf = fi.suffix().toLower();
|
||||
QString ct = "image/jpeg";
|
||||
if (suf == "png") ct = "image/png";
|
||||
else if (suf == "gif") ct = "image/gif";
|
||||
else if (suf == "bmp") ct = "image/bmp";
|
||||
else if (suf == "webp") ct = "image/webp";
|
||||
|
||||
int iw = 0, ih = 0;
|
||||
QImageReader reader(mediaSrc);
|
||||
reader.setAutoTransform(true);
|
||||
if (reader.canRead()) {
|
||||
QSize sz = reader.size();
|
||||
if (sz.width() > 0 && sz.height() > 0) { iw = sz.width(); ih = sz.height(); }
|
||||
}
|
||||
|
||||
struct chat_msg_submit* req = (struct chat_msg_submit*)
|
||||
u_malloc(sizeof(struct chat_msg_submit) + textData.size());
|
||||
memset(req, 0, sizeof(*req));
|
||||
snprintf(req->channel_id, sizeof(req->channel_id), "%s",
|
||||
m_currentChannelId.toUtf8().constData());
|
||||
snprintf(req->content_type, sizeof(req->content_type), "%s", ct.toUtf8().constData());
|
||||
snprintf(req->media_src, sizeof(req->media_src), "%s", mediaSrc.toUtf8().constData());
|
||||
snprintf(req->media_dest, sizeof(req->media_dest), "%s", mediaDest.toUtf8().constData());
|
||||
req->media_copy = (mediaSrc != mediaDest) ? 1 : 0;
|
||||
req->media_video = 0;
|
||||
req->width = iw;
|
||||
req->height = ih;
|
||||
req->data = (uint8_t*)(req + 1);
|
||||
req->data_len = (uint32_t)textData.size();
|
||||
memcpy(req->data, textData.constData(), textData.size());
|
||||
req->timestamp = (uint64_t)ts;
|
||||
gui_bridge_post_uasync_fn(chat_core_submit_trampoline, req);
|
||||
});
|
||||
|
||||
connect(AnimTimer::instance(), &AnimTimer::ticked, this, [this]() {
|
||||
m_view->viewport()->update();
|
||||
});
|
||||
@@ -765,3 +852,53 @@ void MessageList::onAttachmentDownloaded(const QString& chId, int64_t msgId) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
QModelIndex idx = m_view->indexAt(pos);
|
||||
if (!idx.isValid()) return;
|
||||
|
||||
QString ct = idx.data(MsgContentTypeRole).toString();
|
||||
bool isVideo = (ct == "video/mp4");
|
||||
bool isImage = ct.startsWith("image/");
|
||||
if (!isVideo && !isImage) return;
|
||||
|
||||
QString filePath = idx.data(MsgVoiceFileRole).toString();
|
||||
QString displayName = idx.data(MsgFileDisplayNameRole).toString();
|
||||
bool downloaded = !filePath.isEmpty() && QFile::exists(filePath);
|
||||
|
||||
QMenu menu(m_view);
|
||||
QAction* saveAct = menu.addAction(QString::fromUtf8("Сохранить в файл"));
|
||||
saveAct->setEnabled(downloaded);
|
||||
QAction* dlAct = nullptr;
|
||||
if (!downloaded) dlAct = menu.addAction(QString::fromUtf8("Скачать"));
|
||||
|
||||
QAction* chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
|
||||
|
||||
if (chosen == saveAct) {
|
||||
QString base = displayName;
|
||||
if (base.isEmpty()) base = QFileInfo(filePath).fileName();
|
||||
QString dest = QFileDialog::getSaveFileName(this, QString::fromUtf8("Сохранить файл"), base);
|
||||
if (dest.isEmpty()) return;
|
||||
if (!QFile::copy(filePath, dest)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MessageList: save failed src=%s dst=%s",
|
||||
qPrintable(filePath), qPrintable(dest));
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: saved %s -> %s",
|
||||
qPrintable(filePath), qPrintable(dest));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosen == dlAct) {
|
||||
qint64 msgId = idx.data(MsgIdRole).toLongLong();
|
||||
QString chId = idx.data(MsgChannelIdRole).toString();
|
||||
if (chId.isEmpty() || msgId <= 0) return;
|
||||
struct attachment_dl_req* req = (struct attachment_dl_req*)
|
||||
u_malloc(sizeof(struct attachment_dl_req));
|
||||
if (!req) return;
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user