From cc0dcb071e1be7ed42f9ae833b3fc3538c3d2266 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 22 Sep 2026 01:25:55 +0300 Subject: [PATCH] =?UTF-8?q?chat:=20UI=20=D0=BD=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=B7=D0=B2=D1=83=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=B0=20(Android=20toggle=20+=20?= =?UTF-8?q?JNI,=20desktop=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20C=20API)=20+?= =?UTF-8?q?=20trampoline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_core.c | 7 ++ src/chat/chat_core.h | 3 + .../main/java/com/utun/chat/data/NativeLib.kt | 6 ++ .../com/utun/chat/ui/screens/ChatScreen.kt | 6 ++ .../com/utun/chat/viewmodel/ChatViewModel.kt | 14 ++++ .../jni_bridge/android_jni_bridge.c | 69 ++++++++++++++++--- .../jni_bridge/android_jni_bridge.h | 5 ++ tools/chatgui/src/channelsettingsdialog.cpp | 16 ++++- 8 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 8f5946ff..8ed030ea 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -288,6 +288,13 @@ void chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_i chat_sync_broadcast_sound_setting(inst, ch_id, on ? 1 : 0); } +void chat_core_set_sound_on_message_trampoline(void* arg) { + struct chat_sound_arg* a = (struct chat_sound_arg*)arg; + if (!a) return; + chat_core_set_sound_on_message(a->inst, a->ch_id, a->on); + u_free(arg); +} + /* ─── headless API ─── */ static void json_escape(const char* src, char* dst, size_t dst_sz) { diff --git a/src/chat/chat_core.h b/src/chat/chat_core.h index 96abd5b1..2bbbbf35 100644 --- a/src/chat/chat_core.h +++ b/src/chat/chat_core.h @@ -125,6 +125,9 @@ void chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const int chat_core_get_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id); void chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on); +struct chat_sound_arg { struct UTUN_INSTANCE* inst; char ch_id[64]; int on; }; +void chat_core_set_sound_on_message_trampoline(void* arg); + struct save_ui_state_arg { struct UTUN_INSTANCE* inst; char data[256]; }; void chat_core_save_ui_state_trampoline(void* arg); diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt index d2b647be..151e505e 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt @@ -85,6 +85,10 @@ object NativeLib { fun setChatSetting(name: String, value: String) { nativeSetChatSetting(name, value) } fun getChatSettingInt(name: String, def: Int = 0): Int = nativeGetChatSettingInt(name, def) + /* ── Per-channel «play sound on message» ── */ + fun getSoundOnMessage(chId: String): Boolean = nativeGetSoundOnMessage(chId) != 0 + fun setSoundOnMessage(chId: String, on: Boolean) { nativeSetSoundOnMessage(chId, on) } + /** Enable/disable forwarding native C logs to LogManager (expensive, off by default) */ fun subscribeNativeLogs(enabled: Boolean) { nativeSubscribeLogs(enabled) } fun setUdpLogTarget(ip: String, port: Int) { nativeSetUdpLogTarget(ip, port) } @@ -186,6 +190,8 @@ object NativeLib { private external fun nativeSetDebugLevel(category: String, level: String) private external fun nativeSetChatSetting(name: String, value: String) private external fun nativeGetChatSettingInt(name: String, def: Int): Int + private external fun nativeGetSoundOnMessage(chId: String): Int + private external fun nativeSetSoundOnMessage(chId: String, on: Boolean) private external fun nativeSubscribeLogs(enabled: Boolean) private external fun nativeSetUdpLogTarget(ip: String, port: Int) private external fun nativeRegenerateKeys() diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt index beebc114..d4bca457 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt @@ -70,6 +70,9 @@ fun ChatScreen( val overlayShown by RadioOverlayService.isShown.collectAsState() val localTalking by RadioAudioEngine.instance().talkingFlow.collectAsState() + /* звук сообщений канала */ + val soundOn by viewModel.soundOn.collectAsState() + /* attach menu */ var showAttachMenu by remember { mutableStateOf(false) } @@ -140,6 +143,9 @@ fun ChatScreen( IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") } }, actions = { + IconButton(onClick = { viewModel.toggleSound() }) { + Text(if (soundOn) "\uD83D\uDD14" else "\uD83D\uDD15", fontSize = 18.sp) + } IconButton(onClick = { viewModel.toggleRadio() }) { Text(if (radioActive) "\uD83D\uDCFB" else "\uD83D\uDD07", fontSize = 18.sp) } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 3e8c45e8..39a38b8f 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -94,6 +94,9 @@ class ChatViewModel : ViewModel() { private val _currentChannel = MutableStateFlow(null) val currentChannel: StateFlow = _currentChannel + private val _soundOn = MutableStateFlow(true) + val soundOn: StateFlow = _soundOn + private val _pubKey = MutableStateFlow("") val pubKey: StateFlow = _pubKey @@ -857,10 +860,21 @@ class ChatViewModel : ViewModel() { val r = repo ?: return radioOff() /* рацию выключаем при переключении канала */ _currentChannel.value = channel + _soundOn.value = NativeLib.getSoundOnMessage(channel.id) + NotificationSound.setMuted(channel.id, !_soundOn.value) r.connectChannel(channel.id) refreshMessages(channel.id) } + /* ── per-channel «play sound on message» ── */ + fun toggleSound() { + val ch = _currentChannel.value ?: return + val on = !_soundOn.value + _soundOn.value = on + NotificationSound.setMuted(ch.id, !on) + NativeLib.setSoundOnMessage(ch.id, on) + } + /* ── DM (личные сообщения) ── */ fun refreshDmConversations() { diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index ab331912..3deb78df 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -1027,6 +1027,40 @@ void utun_bridge_delete_channel(const char* channel_id) { bridge_log(BLEV_INFO, "delete_channel queued ch=%s", channel_id); } +/* ── Per-channel «play sound on message» ── */ + +struct sound_setting_req { + struct UTUN_INSTANCE* inst; + char ch_id[64]; + int on; +}; + +static void sound_setting_trampoline(void* arg) { + struct sound_setting_req* req = (struct sound_setting_req*)arg; + if (!req || !req->inst) { u_free(arg); return; } + chat_core_set_sound_on_message(req->inst, req->ch_id, req->on); + u_free(arg); +} + +int utun_bridge_get_sound_on_message(const char* channel_id) { + struct UTUN_INSTANCE* inst = instance_lite_get_instance(); + if (!inst || !chat_core_is_initialized(inst) || !channel_id || !channel_id[0]) return 1; + return chat_core_get_sound_on_message(inst, channel_id); +} + +void utun_bridge_set_sound_on_message(const char* channel_id, int on) { + if (!channel_id || !channel_id[0]) { bridge_log(BLEV_ERROR, "set_sound: empty ch_id"); return; } + struct UASYNC* ua = instance_lite_get_uasync(); + if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "set_sound: uasync not running"); return; } + struct sound_setting_req* req = u_calloc(1, sizeof(*req)); + if (!req) { bridge_log(BLEV_ERROR, "set_sound: OOM"); return; } + req->inst = instance_lite_get_instance(); + strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); + req->on = on ? 1 : 0; + uasync_post(ua, sound_setting_trampoline, req); + bridge_log(BLEV_INFO, "set_sound queued ch=%s on=%d", channel_id, on ? 1 : 0); +} + /* ────────────────────────────────────────────────────────────────── * Local sockets state (for auto-mode UI display) * ────────────────────────────────────────────────────────────────── */ @@ -1943,15 +1977,32 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetChatSetting( if (n) (*env)->ReleaseStringUTFChars(env, name, n); } -JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeGetChatSettingInt( - JNIEnv* env, jobject thiz, jstring name, jint def) { - (void)thiz; - const char* n = (*env)->GetStringUTFChars(env, name, NULL); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - int val = chat_setting_get_int(inst, n, (int)def); - if (n) (*env)->ReleaseStringUTFChars(env, name, n); - return (jint)val; -} + JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeGetChatSettingInt( + JNIEnv* env, jobject thiz, jstring name, jint def) { + (void)thiz; + const char* n = (*env)->GetStringUTFChars(env, name, NULL); + struct UTUN_INSTANCE* inst = instance_lite_get_instance(); + int val = chat_setting_get_int(inst, n, (int)def); + if (n) (*env)->ReleaseStringUTFChars(env, name, n); + return (jint)val; + } + +JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeGetSoundOnMessage( + JNIEnv* env, jobject thiz, jstring chId) { + (void)thiz; + const char* c = (*env)->GetStringUTFChars(env, chId, NULL); + int on = utun_bridge_get_sound_on_message(c); + if (c) (*env)->ReleaseStringUTFChars(env, chId, c); + return (jint)on; + } + +JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetSoundOnMessage( + JNIEnv* env, jobject thiz, jstring chId, jboolean on) { + (void)thiz; + const char* c = (*env)->GetStringUTFChars(env, chId, NULL); + utun_bridge_set_sound_on_message(c, on ? 1 : 0); + if (c) (*env)->ReleaseStringUTFChars(env, chId, c); + } JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSubscribeLogs( JNIEnv* env, jobject thiz, jboolean enabled) { diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.h b/tools/chatgui-android/jni_bridge/android_jni_bridge.h index 36a71fbc..363d3ba1 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.h +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.h @@ -176,6 +176,11 @@ void utun_bridge_transfer_admin(const char* channel_id, uint64_t node_id); /* Локально удалить канал (drop таблиц, соединений, медиа, db_sync). */ void utun_bridge_delete_channel(const char* channel_id); +/* ── Per-channel «play sound on message» (троттлинг db_sync на сервере) ── */ + +int utun_bridge_get_sound_on_message(const char* channel_id); +void utun_bridge_set_sound_on_message(const char* channel_id, int on); + /* ── Local sockets state (for auto-mode display) ── */ char* utun_bridge_get_local_sockets_json(void); diff --git a/tools/chatgui/src/channelsettingsdialog.cpp b/tools/chatgui/src/channelsettingsdialog.cpp index 51e5e0b5..4cb8194b 100644 --- a/tools/chatgui/src/channelsettingsdialog.cpp +++ b/tools/chatgui/src/channelsettingsdialog.cpp @@ -9,6 +9,7 @@ extern "C" { #include #include #include +#include ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager* db, QWidget* parent) : QDialog(parent) @@ -102,7 +103,20 @@ void ChannelSettingsDialog::onSave() { } }; saveKey("auto_play_voice", m_autoPlayVoice->isChecked() ? 1 : 0); - saveKey("sound_on_message", m_soundOnMessage->isChecked() ? 1 : 0); saveKey("ptt_mode", m_pttMode->isChecked() ? 1 : 0); + + /* sound_on_message: сохранить + разослать пирам (троттлинг db_sync на сервере) */ + { + int on = m_soundOnMessage->isChecked() ? 1 : 0; + QByteArray chIdBA = m_channelId.toUtf8(); + struct chat_sound_arg* arg = (struct chat_sound_arg*)u_malloc(sizeof(struct chat_sound_arg)); + if (arg) { + memset(arg, 0, sizeof(*arg)); + arg->inst = gui_bridge_get_inst(); + strncpy(arg->ch_id, chIdBA.constData(), sizeof(arg->ch_id) - 1); + arg->on = on; + gui_bridge_post_uasync_fn(chat_core_set_sound_on_message_trampoline, arg); + } + } accept(); }