chatgui/chatgui-android: уведомления со звуком, флаги и прочие улучшения UI
This commit is contained in:
1 parent
6c857ceab8
commit
0f308fdbd7
20 files changed
+292
-32
No files matched your search
@@ -28,6 +28,7 @@ extern "C" {
|
||||
#include <QSplitter>
|
||||
#include <QCloseEvent>
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QClipboard>
|
||||
#include <QStyle>
|
||||
#include <QMessageBox>
|
||||
@@ -38,6 +39,7 @@ extern "C" {
|
||||
#include <cmath>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <csignal>
|
||||
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
@@ -49,12 +51,16 @@ void chat_core_save_ui_state_trampoline(void* arg);
|
||||
|
||||
static MainWindow* s_mainWindow = nullptr;
|
||||
|
||||
/* Сохранение last_channel_id в БД (ui_state), как все остальные UI-настройки.
|
||||
Пишется из utun-потока через trampoline, чтобы не трогать sqlite из GUI-потока. */
|
||||
static void saveLastChannelId(const QString& channelId) {
|
||||
const char key[] = "last_channel_id";
|
||||
QByteArray val = channelId.toUtf8();
|
||||
size_t klen = sizeof(key) - 1, vlen = (size_t)val.size(), total = klen + 1 + vlen + 1;
|
||||
#ifndef _WIN32
|
||||
static volatile std::sig_atomic_t g_signalFlag = 0;
|
||||
static void unixSignalHandler(int) { g_signalFlag = 1; }
|
||||
#endif
|
||||
|
||||
/* Сохранение key/value в БД (ui_state). Пишется из utun-потока через trampoline,
|
||||
чтобы не трогать sqlite из GUI-потока. Буфер формата key\0value\0, ≤256 байт. */
|
||||
static void postUiStateKv(const char* key, const QString& value) {
|
||||
QByteArray val = value.toUtf8();
|
||||
size_t klen = strlen(key), vlen = (size_t)val.size(), total = klen + 1 + vlen + 1;
|
||||
if (total > 256) return;
|
||||
void* arg = u_malloc(total);
|
||||
memcpy(arg, key, klen + 1);
|
||||
@@ -62,6 +68,10 @@ static void saveLastChannelId(const QString& channelId) {
|
||||
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
|
||||
}
|
||||
|
||||
static void saveLastChannelId(const QString& channelId) {
|
||||
postUiStateKv("last_channel_id", channelId);
|
||||
}
|
||||
|
||||
static void onMsgReceivedCallback(const char* ch_id, int ch_id_len, uint64_t author) {
|
||||
if (s_mainWindow)
|
||||
s_mainWindow->onMessageReceived(ch_id, ch_id_len, author);
|
||||
@@ -136,6 +146,14 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
|
||||
setupConnects();
|
||||
}
|
||||
connect(m_autoPlayTimer, &QTimer::timeout, this, &MainWindow::autoPlayTimerTick);
|
||||
|
||||
#ifndef _WIN32
|
||||
std::signal(SIGINT, unixSignalHandler);
|
||||
std::signal(SIGTERM, unixSignalHandler);
|
||||
m_shutdownTimer = new QTimer(this);
|
||||
connect(m_shutdownTimer, &QTimer::timeout, this, &MainWindow::onShutdownCheck);
|
||||
m_shutdownTimer->start(200);
|
||||
#endif
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
@@ -167,6 +185,7 @@ MainWindow::~MainWindow() {
|
||||
|
||||
void MainWindow::setupUi() {
|
||||
auto *splitter = new QSplitter(Qt::Horizontal, this);
|
||||
m_splitter = splitter;
|
||||
|
||||
m_channelList = new ChannelList(m_db, splitter);
|
||||
m_recorder = new AudioRecorder(this);
|
||||
@@ -186,6 +205,49 @@ void MainWindow::setupUi() {
|
||||
setCentralWidget(splitter);
|
||||
}
|
||||
|
||||
void MainWindow::saveWindowGeometry() {
|
||||
QRect g = normalGeometry();
|
||||
if (g.isEmpty()) g = geometry();
|
||||
postUiStateKv("window_geometry",
|
||||
QStringLiteral("%1,%2,%3,%4").arg(g.width()).arg(g.height()).arg(g.x()).arg(g.y()));
|
||||
postUiStateKv("window_maximized", isMaximized() ? QStringLiteral("1") : QStringLiteral("0"));
|
||||
|
||||
if (m_splitter) {
|
||||
const QList<int> sizes = m_splitter->sizes();
|
||||
if (sizes.size() == 3)
|
||||
postUiStateKv("window_splitter",
|
||||
QStringLiteral("%1,%2,%3").arg(sizes[0]).arg(sizes[1]).arg(sizes[2]));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::restoreWindowGeometry() {
|
||||
if (!m_db) return;
|
||||
const QStringList p = m_db->getUiState("window_geometry").split(',');
|
||||
if (p.size() != 4) return;
|
||||
int w = p[0].toInt(), h = p[1].toInt(), x = p[2].toInt(), y = p[3].toInt();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
QRect target(x, y, w, h);
|
||||
bool visible = false;
|
||||
for (const QScreen* s : QGuiApplication::screens()) {
|
||||
if (s->availableGeometry().intersects(target)) { visible = true; break; }
|
||||
}
|
||||
if (!visible) target.moveTo(0, 0);
|
||||
|
||||
setGeometry(target);
|
||||
if (m_db->getUiStateInt("window_maximized", 0))
|
||||
showMaximized();
|
||||
|
||||
if (m_splitter) {
|
||||
const QStringList sp = m_db->getUiState("window_splitter").split(',');
|
||||
if (sp.size() == 3) {
|
||||
int s0 = sp[0].toInt(), s1 = sp[1].toInt(), s2 = sp[2].toInt();
|
||||
if (s0 > 0 && s1 > 0 && s2 > 0)
|
||||
m_splitter->setSizes({s0, s1, s2});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setupNode() {
|
||||
m_node = new UtunNode(this);
|
||||
connect(m_node, &UtunNode::error, this, [](const QString& msg) {
|
||||
@@ -247,12 +309,14 @@ void MainWindow::setupSoundFromConfig() {
|
||||
QString baseDir = QDir::isAbsolutePath(soundDir) ? soundDir : binDir.absoluteFilePath(soundDir);
|
||||
auto resolvePath = [&baseDir](const QString& f) { return f.isEmpty() ? QString() : QDir::cleanPath(baseDir + "/" + f); };
|
||||
|
||||
struct { const char* name; QString file; } events[] = {
|
||||
{"msg_incoming", msgInFile}, {"msg_outgoing", msgOutFile},
|
||||
{"connect", connectFile}, {"disconnect", disconnectFile},
|
||||
};
|
||||
for (auto& e : events) { if (!e.file.isEmpty()) sm->loadSound(e.name, resolvePath(e.file)); }
|
||||
if (msgInFile.isEmpty())
|
||||
sm->loadSound("msg_incoming", ":/sounds/msg_incoming.mp3");
|
||||
else
|
||||
sm->loadSound("msg_incoming", resolvePath(msgInFile));
|
||||
|
||||
if (!msgOutFile.isEmpty()) sm->loadSound("msg_outgoing", resolvePath(msgOutFile));
|
||||
if (!connectFile.isEmpty()) sm->loadSound("connect", resolvePath(connectFile));
|
||||
if (!disconnectFile.isEmpty()) sm->loadSound("disconnect", resolvePath(disconnectFile));
|
||||
if (m_db && m_db->isOpen()) {
|
||||
sm->setEnabled(m_db->getUiStateInt("sound_enabled", 1));
|
||||
sm->setVolume(m_db->getUiStateInt("sound_volume", 80) / 100.0f);
|
||||
@@ -307,6 +371,7 @@ void MainWindow::setupBridgeCallbacks() {
|
||||
if (!s_mainWindow || !s_mainWindow->m_db) return;
|
||||
s_mainWindow->m_db->setDb(chat_core_get_db());
|
||||
s_mainWindow->m_channelList->loadChannels();
|
||||
s_mainWindow->restoreWindowGeometry();
|
||||
QString lastCid = s_mainWindow->m_db->getUiState("last_channel_id");
|
||||
if (!lastCid.isEmpty()) {
|
||||
/* восстанавливаем канал только если он реально существует в БД —
|
||||
@@ -402,9 +467,7 @@ void MainWindow::setupTray() {
|
||||
});
|
||||
|
||||
QAction *quitAction = m_trayMenu->addAction("Quit");
|
||||
connect(quitAction, &QAction::triggered, this, [this]() {
|
||||
if (m_node) m_node->requestStop();
|
||||
});
|
||||
connect(quitAction, &QAction::triggered, this, &MainWindow::requestShutdown);
|
||||
|
||||
m_trayIcon->setContextMenu(m_trayMenu);
|
||||
|
||||
@@ -416,12 +479,24 @@ void MainWindow::setupTray() {
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: closeEvent — hiding to tray");
|
||||
saveWindowGeometry();
|
||||
hide();
|
||||
m_trayIcon->showMessage("vibechat", "Application minimized to tray.",
|
||||
QSystemTrayIcon::Information, 2000);
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void MainWindow::requestShutdown() {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: requestShutdown — saving geometry and stopping node");
|
||||
saveWindowGeometry();
|
||||
if (m_node) m_node->requestStop();
|
||||
}
|
||||
|
||||
void MainWindow::onShutdownCheck() {
|
||||
if (g_signalFlag)
|
||||
requestShutdown();
|
||||
}
|
||||
|
||||
void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) {
|
||||
if (reason == QSystemTrayIcon::Trigger ||
|
||||
reason == QSystemTrayIcon::DoubleClick) {
|
||||
|
||||
Reference in new issue
Block a user