feat: 重构主界面与设置界面,添加日志管理功能

- 升级 compileSdk 和 targetSdk 至 36,Java 版本至 17
- 提取字符串资源,实现界面国际化
- 重构主界面布局,移除冗余日志显示,改为按钮导航
- 新增 LogManager 对象,提供日志收集与显示功能
- 增强设置界面,添加 IP 保存、日志显示/隐藏及键盘管理功能
- 优化用户体验,统一界面元素尺寸与交互逻辑
This commit is contained in:
2026-03-10 15:39:27 +08:00
parent cd6f1699ab
commit 71d84f2043
7 changed files with 236 additions and 77 deletions

View File

@@ -5,12 +5,12 @@ plugins {
android {
namespace = "com.example.lzwcai_terminal_temi"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "com.example.lzwcai_terminal_temi"
minSdk = 23
targetSdk = 35
targetSdk = 36
versionCode = 1
versionName = "1.0"
@@ -27,11 +27,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "17"
}
buildFeatures {
viewBinding = true

View File

@@ -0,0 +1,67 @@
package com.example.lzwcai_terminal_temi
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import java.io.BufferedReader
import java.io.InputStreamReader
object LogManager {
private val _logs = MutableLiveData<String>("")
val logs: LiveData<String> = _logs
private val logBuffer = StringBuffer()
private var isReading = false
private val currentPid = android.os.Process.myPid().toString()
fun startLogcatListener() {
if (isReading) return
isReading = true
Thread {
try {
// 清除之前的日志缓存
Runtime.getRuntime().exec("logcat -c")
// 开始读取日志过滤当前进程ID
val process = Runtime.getRuntime().exec("logcat -v threadtime")
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
line?.let {
if (it.contains(currentPid)) {
// 简单的过滤只显示包含当前PID的行
// 可以根据需要进一步处理日志格式
updateLog(it)
}
}
}
} catch (e: Exception) {
Log.e("LogManager", "Error reading logcat", e)
updateLog("Error reading logs: ${e.message}")
}
}.start()
}
private fun updateLog(msg: String) {
// 限制日志缓冲区大小,避免内存溢出
if (logBuffer.length > 50000) {
logBuffer.delete(0, 10000)
}
logBuffer.append("$msg\n")
_logs.postValue(logBuffer.toString())
}
fun clearLogs() {
logBuffer.setLength(0)
_logs.postValue("")
// 也可以尝试清除系统日志缓存,但通常需要权限或只能清除应用自己的
Thread {
try {
Runtime.getRuntime().exec("logcat -c")
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
}

View File

@@ -4,67 +4,40 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding
import com.robotemi.sdk.Robot
import java.util.concurrent.Executors
class MainActivity : AppCompatActivity() {
private lateinit var robot: Robot
private lateinit var binding: ActivityMainBinding
private val executorService = Executors.newSingleThreadExecutor()
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 启动日志监听
LogManager.startLogcatListener()
// 隐藏软键盘
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
// 获取 Robot 实例
robot = Robot.getInstance()
// 初始化日志视图
binding.tvLog.movementMethod = ScrollingMovementMethod.getInstance()
// 设置按钮点击事件
binding.btnSettings.setOnClickListener {
startActivity(Intent(this, SettingsActivity::class.java))
}
// --- 轻量化处理:移除不必要的监听器 ---
// 移除了 FaceRecognized, Telepresence, GreetMode 等监听器
// 仅保留基础的权限和运动状态监听作为示例
robot.addOnRequestPermissionResultListener { permission, grantResult, _ ->
printLog("Permission: $permission, Result: $grantResult")
}
robot.addOnMovementStatusChangedListener { type, status ->
printLog("Movement: $type, Status: $status")
}
// 读取并显示配置的 IP
// 读取配置的 IP 并记录日志
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
val ip = prefs.getString("network_ip", "未设置")
printLog("应用启动完成。目标 IP: $ip")
}
private fun printLog(msg: String) {
runOnUiThread {
binding.tvLog.append("$msg\n")
}
}
override fun onDestroy() {
super.onDestroy()
// 建议在销毁时移除监听器,避免内存泄漏 (虽然 Temi SDK 通常会处理,但显式移除是好习惯)
// robot.removeOn...
Log.i("MainActivity", "应用启动完成。目标 IP: $ip")
}
}

View File

@@ -2,6 +2,10 @@ package com.example.lzwcai_terminal_temi
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.lzwcai_terminal_temi.databinding.ActivitySettingsBinding
@@ -15,20 +19,67 @@ class SettingsActivity : AppCompatActivity() {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
// 默认隐藏软键盘
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
val savedIp = prefs.getString("network_ip", "")
binding.etIpAddress.setText(savedIp)
// 点击外部隐藏键盘
binding.root.setOnClickListener {
hideKeyboard()
}
binding.btnSave.setOnClickListener {
hideKeyboard()
val ip = binding.etIpAddress.text.toString().trim()
// Here you could add regex validation for IP address if needed
if (ip.isNotEmpty()) {
prefs.edit().putString("network_ip", ip).apply()
Toast.makeText(this, "IP Saved: $ip", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.msg_ip_saved, ip), Toast.LENGTH_SHORT).show()
Log.i("SettingsActivity", "IP Saved: $ip")
finish()
} else {
Toast.makeText(this, "Please enter a valid IP", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.msg_invalid_ip), Toast.LENGTH_SHORT).show()
Log.w("SettingsActivity", "Invalid IP attempt")
}
}
binding.btnBack.setOnClickListener {
hideKeyboard()
finish()
}
binding.btnToggleLogs.setOnClickListener {
hideKeyboard()
if (binding.tvLog.visibility == View.VISIBLE) {
binding.tvLog.visibility = View.GONE
binding.btnToggleLogs.text = getString(R.string.btn_show_logs)
} else {
binding.tvLog.visibility = View.VISIBLE
binding.btnToggleLogs.text = getString(R.string.btn_hide_logs)
}
}
LogManager.logs.observe(this) { logs ->
binding.tvLog.text = logs
// 自动滚动到底部
binding.tvLog.post {
if (binding.tvLog.layout != null) {
val scrollAmount = binding.tvLog.layout.getLineTop(binding.tvLog.lineCount) - binding.tvLog.height
if (scrollAmount > 0)
binding.tvLog.scrollTo(0, scrollAmount)
else
binding.tvLog.scrollTo(0, 0)
}
}
}
}
private fun hideKeyboard() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
currentFocus?.let {
imm.hideSoftInputFromWindow(it.windowToken, 0)
}
}
}

View File

@@ -7,25 +7,28 @@
tools:context=".MainActivity">
<TextView
android:id="@+id/tvLog"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="16dp"
android:scrollbars="vertical"
android:text="Logs will appear here..."
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_main"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/btnSettings"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="@+id/btnSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Settings"
android:layout_width="300dp"
android:layout_height="100dp"
android:layout_marginTop="32dp"
android:text="@string/btn_settings"
android:textSize="32sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,30 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
android:fillViewport="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Network IP Configuration"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/etIpAddress"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter IP Address (e.g., 192.168.1.100)"
android:inputType="textUri"/>
android:orientation="vertical"
android:padding="24dp">
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/title_settings"
android:textSize="32sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/label_ip_config"
android:textSize="24sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etIpAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="@string/hint_ip_address"
android:inputType="number|numberDecimal"
android:digits="0123456789."
android:minHeight="60dp"
android:textSize="24sp" />
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="16dp"
android:text="@string/btn_save"
android:textSize="24sp" />
<Button
android:id="@+id/btnBack"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="32dp"
android:text="@string/btn_back"
android:textSize="24sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="24dp"
android:background="#CCCCCC" />
<Button
android:id="@+id/btnToggleLogs"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="16dp"
android:text="@string/btn_show_logs"
android:textSize="24sp" />
<TextView
android:id="@+id/tvLog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#EEEEEE"
android:padding="16dp"
android:scrollbars="vertical"
android:text="@string/log_placeholder"
android:textSize="18sp"
android:visibility="gone" />
</LinearLayout>
</ScrollView>

View File

@@ -1,3 +1,16 @@
<resources>
<string name="app_name">lzwcai-terminal-temi</string>
<string name="title_main">主界面</string>
<string name="btn_settings">设置</string>
<string name="title_settings">设置</string>
<string name="label_ip_config">网络 IP 配置</string>
<string name="hint_ip_address">请输入 IP 地址 (例如 192.168.1.100)</string>
<string name="btn_save">保存</string>
<string name="btn_back">返回主界面</string>
<string name="btn_show_logs">显示日志</string>
<string name="btn_hide_logs">隐藏日志</string>
<string name="msg_ip_saved">IP 已保存: %1$s</string>
<string name="msg_invalid_ip">请输入有效的 IP 地址</string>
<string name="log_init">应用启动完成。目标 IP: %1$s</string>
<string name="log_placeholder">日志将显示在这里...</string>
</resources>