From 5737a66afd56e36bcdd57632f45d1bd5fef7b0f1 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Mon, 14 Sep 2026 20:40:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(android):=20=E8=AE=BE=E7=BD=AE=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96=EF=BC=88=E6=8E=A8=E6=B5=81=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E4=B8=8E=E8=BF=9E=E6=8E=A5=E4=BF=A1=E6=81=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/opendance/studio/data/AppSettings.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/src/main/java/com/opendance/studio/data/AppSettings.kt diff --git a/app/src/main/java/com/opendance/studio/data/AppSettings.kt b/app/src/main/java/com/opendance/studio/data/AppSettings.kt new file mode 100644 index 0000000..a5589a7 --- /dev/null +++ b/app/src/main/java/com/opendance/studio/data/AppSettings.kt @@ -0,0 +1,94 @@ +package com.opendance.studio.data + +import android.content.Context +import androidx.core.content.edit + +/** + * 应用设置(SharedPreferences)。 + * + * 契约 websocket-keypoints.md §3.3 的 config 字段与这里的前四项一一对应。 + */ +class AppSettings(context: Context) { + + private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + /** Windows 客户端主机地址 */ + var host: String + get() = prefs.getString(KEY_HOST, DEFAULT_HOST) ?: DEFAULT_HOST + set(value) = prefs.edit { putString(KEY_HOST, value.trim()) } + + var port: Int + get() = prefs.getInt(KEY_PORT, DEFAULT_PORT) + set(value) = prefs.edit { putInt(KEY_PORT, value.coerceIn(1, 65535)) } + + /** 目标帧率(契约 config.targetFps) */ + var targetFps: Int + get() = prefs.getInt(KEY_TARGET_FPS, DEFAULT_FPS) + set(value) = prefs.edit { putInt(KEY_TARGET_FPS, value.coerceIn(5, 60)) } + + /** 是否镜像画面(契约 flags.bit0 / config.mirror) */ + var mirror: Boolean + get() = prefs.getBoolean(KEY_MIRROR, true) + set(value) = prefs.edit { putBoolean(KEY_MIRROR, value) } + + /** 仅上半身模式(契约 flags.bit2) */ + var upperBodyOnly: Boolean + get() = prefs.getBoolean(KEY_UPPER_BODY, false) + set(value) = prefs.edit { putBoolean(KEY_UPPER_BODY, value) } + + /** 分辨率档位,取值见 [Resolutions] */ + var resolution: String + get() = prefs.getString(KEY_RESOLUTION, Resolutions.MEDIUM) ?: Resolutions.MEDIUM + set(value) = prefs.edit { putString(KEY_RESOLUTION, value) } + + /** 上次连接的设备名(hello 上报用) */ + var deviceLabel: String + get() = prefs.getString(KEY_DEVICE_LABEL, "") ?: "" + set(value) = prefs.edit { putString(KEY_DEVICE_LABEL, value) } + + /** 自动重连 */ + var autoReconnect: Boolean + get() = prefs.getBoolean(KEY_AUTO_RECONNECT, true) + set(value) = prefs.edit { putBoolean(KEY_AUTO_RECONNECT, value) } + + fun webSocketUrl(): String = "ws://$host:$port/pose" + + companion object { + const val PREFS_NAME = "opendance_settings" + const val DEFAULT_HOST = "192.168.1.100" + const val DEFAULT_PORT = 8765 + const val DEFAULT_FPS = 30 + + private const val KEY_HOST = "host" + private const val KEY_PORT = "port" + private const val KEY_TARGET_FPS = "target_fps" + private const val KEY_MIRROR = "mirror" + private const val KEY_UPPER_BODY = "upper_body_only" + private const val KEY_RESOLUTION = "resolution" + private const val KEY_DEVICE_LABEL = "device_label" + private const val KEY_AUTO_RECONNECT = "auto_reconnect" + } +} + +/** 分辨率档位 —— 与契约 config.resolution 的取值格式一致 */ +object Resolutions { + const val LOW = "480x360" + const val MEDIUM = "640x480" + const val HIGH = "1280x720" + + val ALL = listOf(LOW, MEDIUM, HIGH) + + /** 返回 (宽, 高) */ + fun parse(value: String): Pair { + val parts = value.split("x") + val w = parts.getOrNull(0)?.toIntOrNull() ?: 640 + val h = parts.getOrNull(1)?.toIntOrNull() ?: 480 + return w to h + } + + fun label(value: String): String = when (value) { + LOW -> "低(480×360,最省电)" + HIGH -> "高(1280×720,最清晰)" + else -> "中(640×480,推荐)" + } +}