feat: 重构主界面与设置界面,添加日志管理功能
- 升级 compileSdk 和 targetSdk 至 36,Java 版本至 17 - 提取字符串资源,实现界面国际化 - 重构主界面布局,移除冗余日志显示,改为按钮导航 - 新增 LogManager 对象,提供日志收集与显示功能 - 增强设置界面,添加 IP 保存、日志显示/隐藏及键盘管理功能 - 优化用户体验,统一界面元素尺寸与交互逻辑
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<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"/>
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/title_settings"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<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:hint="Enter IP Address (e.g., 192.168.1.100)"
|
||||
android:inputType="textUri"/>
|
||||
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="wrap_content"
|
||||
android:text="Save"
|
||||
android:layout_marginTop="16dp"/>
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/btn_save"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
||||
<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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user