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 601e96f7..df3c603a 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 @@ -111,6 +111,7 @@ object NativeLib { fun feedbackCommand(json: String, requestId: Long = 0): Boolean = nativeFeedbackCommand(json.toByteArray(Charsets.UTF_8), requestId) fun clearDatabase(): Boolean = nativeClearDatabase() fun setDebugLevel(category: String, level: String) { nativeSetDebugLevel(category, level) } + fun getDebugCategories(): List = nativeGetDebugCategories()?.split(",")?.filter { it.isNotEmpty() } ?: emptyList() /* ── Chat settings (live update via C side) ── */ fun setChatSetting(name: String, value: String) { nativeSetChatSetting(name, value) } @@ -294,6 +295,7 @@ object NativeLib { private external fun nativeGetMessages(channelId: String, limit: Int, anchor: ByteArray, exact: Boolean): String? private external fun nativeFeedbackCommand(json: ByteArray, requestId: Long): Boolean private external fun nativeSetDebugLevel(category: String, level: String) + private external fun nativeGetDebugCategories(): 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 diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt index 41749098..9aeba5ea 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt @@ -60,14 +60,7 @@ import kotlinx.coroutines.withContext import java.util.UUID import java.io.IOException -private val LEVELS = listOf("NONE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE") -private val CATEGORIES = listOf( - "sys", "connection", "etcp", "crypto", - "config", "tun", "routing", "timers", "bgp", "chat", - "socket", "control", "dump", "traffic", "debug", "general", - "nat", "keepalive", "media", "etcp_route", "etcp_dump", - "chat_sync", "member_sync", "proxy", "radio", "vad" -) +private val LEVELS = listOf("DISABLED", "NONE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE") private val PROTOCOLS = listOf("tcp", "udp") @OptIn(ExperimentalMaterial3Api::class) @@ -173,6 +166,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, var showClearDb by remember { mutableStateOf(false) } var clearingDb by remember { mutableStateOf(false) } var catLevels by remember { mutableStateOf>(emptyMap()) } + val debugCategories = remember { NativeLib.getDebugCategories() } var servers by remember { mutableStateOf>(emptyList()) } var availableIfaces by remember { mutableStateOf>>(emptyList()) } var connectionMode by remember { mutableStateOf("manual") } @@ -219,11 +213,10 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, } } val setDebug = { level: String -> - onDirty(true); NativeLib.setDebugLevel("", level) + NativeLib.setDebugLevel("", level) scope.launch { provider.setValue("debug.console_level", level) }; debugLevel = level } val setCatLevel = { cat: String, lvl: String -> - onDirty(true) val updated = catLevels.toMutableMap() if (lvl == "") updated.remove(cat) else updated[cat] = lvl catLevels = updated @@ -474,7 +467,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, } } if (showDebugCategories) { - items(CATEGORIES, key = { "debug-$it" }) { cat -> + items(debugCategories, key = { "debug-$it" }) { cat -> val current = catLevels[cat] ?: "" DebugLevelDropdown(label = cat, current = current.ifEmpty { stringResource(R.string.global_level_hint) }, onSelect = { setCatLevel(cat, if (it == "NONE") "" else it) }, compact = true) @@ -1141,10 +1134,10 @@ private fun DebugLevelDropdown(label: String, current: String, onSelect: (String if (compact) { Row(verticalAlignment = Alignment.CenterVertically) { Text(label, modifier = Modifier.width(120.dp), style = MaterialTheme.typography.labelMedium) - ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { + ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }, modifier = Modifier.weight(1f)) { OutlinedTextField(current, {}, readOnly = true, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, - modifier = Modifier.menuAnchor().height(48.dp).weight(1f), singleLine = true) + modifier = Modifier.menuAnchor().fillMaxWidth().heightIn(min = 56.dp), singleLine = true) ExposedDropdownMenu(expanded, onDismissRequest = { expanded = false }) { LEVELS.forEach { lvl -> DropdownMenuItem(text = { Text(lvl) }, onClick = { onSelect(lvl); expanded = false }) @@ -1153,10 +1146,10 @@ private fun DebugLevelDropdown(label: String, current: String, onSelect: (String } } } else { - ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { + ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }, modifier = Modifier.fillMaxWidth()) { OutlinedTextField(current, {}, readOnly = true, label = { Text(stringResource(R.string.debug_level_label, label)) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, - modifier = Modifier.menuAnchor(), singleLine = true) + modifier = Modifier.menuAnchor().fillMaxWidth().heightIn(min = 56.dp), singleLine = true) ExposedDropdownMenu(expanded, onDismissRequest = { expanded = false }) { LEVELS.forEach { lvl -> DropdownMenuItem(text = { Text(lvl) }, onClick = { onSelect(lvl); expanded = false }) diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 93740728..6ca893ee 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -671,13 +671,14 @@ int utun_bridge_dm_send_file(const char* conv_id, const char* path, const char* } void utun_bridge_set_debug_level(const char* category, const char* level) { - if (!level) return; + if (!level) { bridge_log(BLEV_ERROR, "Debug level: missing level"); return; } if (!category || !category[0]) { debug_level_t lvl = debug_level_from_name(level); debug_set_level(lvl); } else { debug_set_category_level_by_name(category, level); } + bridge_log(BLEV_INFO, "Debug level applied: %s=%s", category && category[0] ? category : "global", level); } void utun_bridge_chat_setting_set(const char* name, const char* value) { @@ -2190,6 +2191,22 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeConnectMemberNode if (ch) (*env)->ReleaseStringUTFChars(env, channelId, ch); } +JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetDebugCategories(JNIEnv* env, jobject thiz) { + (void)thiz; + struct strbuf names = strbuf_new(); + for (int category = 1; category < DEBUG_CATEGORY_COUNT; category++) { + const char* name = debug_get_category_name(category); + if (strcmp(name, "UNKNOWN") == 0) continue; + if (strbuf_addf(&names, "%s%s", names.len ? "," : "", name) < 0) { + bridge_log(BLEV_ERROR, "Debug categories: allocation failed"); + strbuf_free(&names); return NULL; + } + } + jstring result = (*env)->NewStringUTF(env, strbuf_str(&names)); + if (!result) bridge_log(BLEV_ERROR, "Debug categories: Java string allocation failed"); + strbuf_free(&names); return result; +} + JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetDebugLevel( JNIEnv* env, jobject thiz, jstring category, jstring level) { (void)thiz;