Browse Source

chat: UI настройки звука канала (Android toggle + JNI, desktop через C API) + trampoline

proxy
evgeny 2 weeks ago
parent
commit
cc0dcb071e
  1. 7
      src/chat/chat_core.c
  2. 3
      src/chat/chat_core.h
  3. 6
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt
  4. 6
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt
  5. 14
      tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt
  6. 69
      tools/chatgui-android/jni_bridge/android_jni_bridge.c
  7. 5
      tools/chatgui-android/jni_bridge/android_jni_bridge.h
  8. 16
      tools/chatgui/src/channelsettingsdialog.cpp

7
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) {

3
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);

6
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()

6
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)
}

14
tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

@ -94,6 +94,9 @@ class ChatViewModel : ViewModel() {
private val _currentChannel = MutableStateFlow<Channel?>(null)
val currentChannel: StateFlow<Channel?> = _currentChannel
private val _soundOn = MutableStateFlow(true)
val soundOn: StateFlow<Boolean> = _soundOn
private val _pubKey = MutableStateFlow("")
val pubKey: StateFlow<String> = _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() {

69
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) {

5
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);

16
tools/chatgui/src/channelsettingsdialog.cpp

@ -9,6 +9,7 @@ extern "C" {
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <cstring>
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();
}

Loading…
Cancel
Save