开发者
This commit is contained in:
parent
a87aac59ce
commit
3302225e74
|
|
@ -47,11 +47,17 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.StandAPP" />
|
||||
|
||||
<activity
|
||||
android:name=".DeveloperActivity"
|
||||
android:exported="false"
|
||||
android:label="开发者模式"
|
||||
android:theme="@style/Theme.StandAPP" />
|
||||
|
||||
<activity
|
||||
android:name=".printer.PrintTestActivity"
|
||||
android:exported="false"
|
||||
android:label="打印测试"
|
||||
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
|
||||
android:theme="@style/Theme.StandAPP" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.kingway.ptt.pttActivity"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package com.stand.standapp
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class DeveloperActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_developer)
|
||||
|
||||
// 左上角退出
|
||||
findViewById<ImageView>(R.id.btn_close).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
// PTT 测试
|
||||
findViewById<LinearLayout>(R.id.item_ptt).setOnClickListener {
|
||||
startActivity(Intent(this, com.example.kingway.ptt.pttActivity::class.java))
|
||||
}
|
||||
|
||||
// 打印测试
|
||||
findViewById<LinearLayout>(R.id.item_printer).setOnClickListener {
|
||||
startActivity(Intent(this, com.stand.standapp.printer.PrintTestActivity::class.java))
|
||||
}
|
||||
|
||||
// 崩溃模拟
|
||||
findViewById<LinearLayout>(R.id.item_crash_test).setOnClickListener {
|
||||
androidx.appcompat.app.AlertDialog.Builder(this)
|
||||
.setTitle("确认崩溃测试?")
|
||||
.setMessage("应用将立即崩溃并尝试记录日志,确定继续吗?")
|
||||
.setPositiveButton("确定") { _, _ ->
|
||||
throw RuntimeException("这是一次开发者手动触发的模拟崩溃测试")
|
||||
}
|
||||
.setNegativeButton("取消", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
// 加载调试信息
|
||||
loadDebugInfo()
|
||||
}
|
||||
|
||||
private fun loadDebugInfo() {
|
||||
val tvInfo = findViewById<TextView>(R.id.tv_debug_info)
|
||||
val info = StringBuilder()
|
||||
info.append("设备型号: ${Build.MODEL}\n")
|
||||
info.append("系统版本: Android ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})\n")
|
||||
info.append("架构(ABI): ${Build.SUPPORTED_ABIS.joinToString(", ")}\n")
|
||||
info.append("运行模式: ${if (android.os.Process.is64Bit()) "64位" else "32位兼容模式"}\n")
|
||||
info.append("服务器地址: ${AppConfig.getServerUrl(this)}\n")
|
||||
info.append("串口路径: ${AppConfig.getSerialPort(this)}")
|
||||
|
||||
tvInfo.text = info.toString()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
package com.stand.standapp
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import timber.log.Timber
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.stand.standapp.printer.PrinterManager
|
||||
import okhttp3.*
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
|
@ -23,9 +24,9 @@ class LoginActivity : AppCompatActivity() {
|
|||
val etPassword = findViewById<EditText>(R.id.et_password)
|
||||
val cbRemember = findViewById<CheckBox>(R.id.cb_remember)
|
||||
val btnLogin = findViewById<Button>(R.id.btn_login)
|
||||
val btnPttExample = findViewById<Button>(R.id.btn_ptt_example)
|
||||
val btnPrinterTest = findViewById<Button>(R.id.btn_printer_test)
|
||||
val btnExit = findViewById<Button>(R.id.btn_exit)
|
||||
val ivSettings = findViewById<ImageView>(R.id.iv_settings)
|
||||
val tvCopyright = findViewById<TextView>(R.id.tv_copyright)
|
||||
|
||||
// 1. 加载保存的状态和凭据
|
||||
val isRemember = AppConfig.isRememberLogin(this)
|
||||
|
|
@ -54,35 +55,38 @@ class LoginActivity : AppCompatActivity() {
|
|||
performLogin(username, password, cbRemember.isChecked)
|
||||
}
|
||||
|
||||
// 4. PTT 示例按钮点击事件
|
||||
btnPttExample.setOnClickListener {
|
||||
try {
|
||||
val intent = Intent(this, com.example.kingway.ptt.pttActivity::class.java)
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "打开 PTT 示例失败: ${e.message}", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
// 4. 退出按钮
|
||||
btnExit.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
// 5. 打印测试按钮点击事件
|
||||
btnPrinterTest.setOnClickListener {
|
||||
try {
|
||||
val intent = Intent(this, com.stand.standapp.printer.PrintTestActivity::class.java)
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "打开打印测试失败: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
// 5. 彩蛋:点击底部版权 10 次进入开发者模式
|
||||
var clickCount = 0
|
||||
var lastClickTime = 0L
|
||||
tvCopyright.setOnClickListener {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
// 连续点击判定间隔为 500ms
|
||||
if (currentTime - lastClickTime < 500) {
|
||||
clickCount++
|
||||
} else {
|
||||
clickCount = 1
|
||||
}
|
||||
lastClickTime = currentTime
|
||||
|
||||
if (clickCount >= 10) {
|
||||
clickCount = 0
|
||||
Toast.makeText(this, "开发者模式已激活", Toast.LENGTH_SHORT).show()
|
||||
startActivity(Intent(this, DeveloperActivity::class.java))
|
||||
} else if (clickCount > 5) {
|
||||
Toast.makeText(this, "再点击 ${10 - clickCount} 次进入调试模式", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行真实接口登录
|
||||
*/
|
||||
private fun performLogin(username: String, password: String, remember: Boolean) {
|
||||
val serverUrl = AppConfig.getServerUrl(this)
|
||||
val loginUrl = "$serverUrl/api/auth/login"
|
||||
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQwMTU2MSwianRpIjoiMjM0NjdiZGMtNTY0Zi00NmY0LWJhZTgtNzJhZmQ1MzhmYmZhIiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.4Zf1eiKcUjGNoFylEzmZ2no1kJY-44CQ8QWmMA_UxvY"
|
||||
|
||||
|
||||
val body = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("username", username)
|
||||
|
|
@ -92,7 +96,6 @@ class LoginActivity : AppCompatActivity() {
|
|||
val request = Request.Builder()
|
||||
.url(loginUrl)
|
||||
.post(body)
|
||||
.addHeader("Authorization", "Bearer $bearerToken")
|
||||
.build()
|
||||
|
||||
val loadingDialog = AlertDialog.Builder(this)
|
||||
|
|
@ -117,7 +120,6 @@ class LoginActivity : AppCompatActivity() {
|
|||
val json = JSONObject(responseBody ?: "")
|
||||
val code = json.optInt("code", -1)
|
||||
if (code == 0) {
|
||||
// 登录成功
|
||||
AppConfig.setRememberLogin(this@LoginActivity, remember)
|
||||
if (remember) {
|
||||
AppConfig.setSavedUsername(this@LoginActivity, username)
|
||||
|
|
@ -127,12 +129,12 @@ class LoginActivity : AppCompatActivity() {
|
|||
AppConfig.setSavedPassword(this@LoginActivity, "")
|
||||
}
|
||||
|
||||
Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
|
||||
|
||||
// 初始化打印机 (自动重连)
|
||||
com.stand.standapp.printer.PrinterManager.init(this@LoginActivity)
|
||||
|
||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||
Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
|
||||
|
||||
// 初始化打印机 (异步)
|
||||
PrinterManager.init(this@LoginActivity)
|
||||
|
||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||
finish()
|
||||
} else {
|
||||
val msg = json.optString("msg", "登录失败")
|
||||
|
|
@ -153,7 +155,6 @@ class LoginActivity : AppCompatActivity() {
|
|||
val etSerialPort = dialogView.findViewById<EditText>(R.id.et_serial_port)
|
||||
val btnReset = dialogView.findViewById<TextView>(R.id.btn_reset)
|
||||
|
||||
// 初始化数据
|
||||
etServerUrl.setText(AppConfig.getServerUrl(this))
|
||||
etSerialPort.setText(AppConfig.getSerialPort(this))
|
||||
|
||||
|
|
@ -190,10 +191,9 @@ class LoginActivity : AppCompatActivity() {
|
|||
|
||||
dialog.show()
|
||||
|
||||
// 强制设置对话框宽度
|
||||
dialog.window?.let { window ->
|
||||
val params = window.attributes
|
||||
params.width = (resources.displayMetrics.widthPixels * 0.75).toInt() // 占据屏幕 75% 宽度
|
||||
params.width = (resources.displayMetrics.widthPixels * 0.95).toInt()
|
||||
window.attributes = params
|
||||
}
|
||||
}
|
||||
|
|
@ -202,10 +202,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
AlertDialog.Builder(this)
|
||||
.setTitle("提示")
|
||||
.setMessage("确定要退出应用吗?")
|
||||
.setPositiveButton("确定") { _, _ ->
|
||||
@Suppress("DEPRECATION")
|
||||
super.onBackPressed()
|
||||
}
|
||||
.setPositiveButton("确定") { _, _ -> finish() }
|
||||
.setNegativeButton("取消", null)
|
||||
.show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,58 +24,99 @@ object LogManager {
|
|||
Timber.plant(Timber.DebugTree())
|
||||
|
||||
try {
|
||||
logDir = File(context.getExternalFilesDir(null), "logs")
|
||||
if (logDir?.exists() == false) logDir?.mkdirs()
|
||||
// 1. 路径确定
|
||||
val baseDir = context.getExternalFilesDir(null)
|
||||
logDir = if (baseDir != null) {
|
||||
File(baseDir, "logs")
|
||||
} else {
|
||||
File(context.filesDir, "logs")
|
||||
}
|
||||
|
||||
if (logDir?.exists() == false) {
|
||||
val created = logDir?.mkdirs()
|
||||
android.util.Log.i("LogManager", "Directory created: $created at ${logDir?.absolutePath}")
|
||||
}
|
||||
|
||||
// 立即清理旧日志
|
||||
logDir?.let { cleanOldLogs(it) }
|
||||
// 2. 强行创建一个“自检”日志文件,不使用异步线程池
|
||||
logDir?.let { dir ->
|
||||
val testFile = File(dir, "startup_check.txt")
|
||||
try {
|
||||
val fos = FileOutputStream(testFile, true)
|
||||
fos.write("Logging started at ${Date()}\n".toByteArray())
|
||||
fos.close()
|
||||
android.util.Log.i("LogManager", "Startup check file written successfully")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LogManager", "FAILED to write startup check file", e)
|
||||
}
|
||||
}
|
||||
|
||||
logDir?.let {
|
||||
cleanOldLogs(it)
|
||||
Timber.plant(FileLoggingTree(it))
|
||||
}
|
||||
|
||||
// 挂载文件日志树
|
||||
logDir?.let { Timber.plant(FileLoggingTree(it)) }
|
||||
|
||||
// 【核心增强】接管全局异常,确保崩溃信息能写进文件
|
||||
setupCrashHandler()
|
||||
|
||||
Timber.tag("LogManager").i("Logging system initialized and CrashHandler attached")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LogManager", "Init failed", e)
|
||||
android.util.Log.e("LogManager", "Init global failure", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCrashHandler() {
|
||||
// 1. 捕获所有普通线程、子线程的未捕获异常
|
||||
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||
// 强制同步写入崩溃日志
|
||||
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())
|
||||
val crashInfo = "\n\n=== FATAL CRASH ===\n" +
|
||||
"Time: $timestamp\n" +
|
||||
"Thread: ${thread.name}\n" +
|
||||
"Stacktrace:\n${throwable.stackTraceToString()}\n" +
|
||||
"====================\n\n"
|
||||
|
||||
logDir?.let { dir ->
|
||||
val logFile = File(dir, SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + ".log")
|
||||
saveCrashToFile(thread, throwable)
|
||||
defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1)
|
||||
}
|
||||
|
||||
// 2. 捕获主线程 (UI 线程) 的所有异常
|
||||
// 这种方式能捕获点击事件、生命周期回调中发生的所有崩溃
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||
while (true) {
|
||||
try {
|
||||
FileOutputStream(logFile, true).use { it.write(crashInfo.toByteArray()) }
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LogManager", "Failed to write crash log", e)
|
||||
android.os.Looper.loop()
|
||||
} catch (e: Throwable) {
|
||||
saveCrashToFile(android.os.Looper.getMainLooper().thread, e)
|
||||
// 如果你希望应用在崩溃后不闪退而是尝试恢复,可以不抛出,但建议这里还是交还给系统处理比较稳妥
|
||||
// 这里我们为了记录完日志后让系统正常走关闭流程
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// 交还给系统处理(弹出“应用已停止”对话框)
|
||||
defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanOldLogs(logDir: File) {
|
||||
private fun saveCrashToFile(thread: Thread, throwable: Throwable) {
|
||||
// 使用同步写入,确保磁盘 IO 优先
|
||||
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())
|
||||
val crashInfo = "\n\nCRASH_ERROR_REPORT\n" +
|
||||
"Time: $timestamp\n" +
|
||||
"Thread: ${thread.name}\n" +
|
||||
"Cause: ${throwable.cause}\n" +
|
||||
"Message: ${throwable.message}\n" +
|
||||
"Full Stacktrace:\n${throwable.stackTraceToString()}\n" +
|
||||
"-------------------\n\n"
|
||||
|
||||
logDir?.let { dir ->
|
||||
try {
|
||||
val logFile = File(dir, SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + ".log")
|
||||
val fos = FileOutputStream(logFile, true)
|
||||
fos.write(crashInfo.toByteArray())
|
||||
fos.flush()
|
||||
fos.fd.sync()
|
||||
fos.close()
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LogManager", "Failed to save crash log", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanOldLogs(dir: File) {
|
||||
executor.execute {
|
||||
try {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.add(Calendar.DAY_OF_YEAR, -MAX_DAYS)
|
||||
val thresholdDate = calendar.time
|
||||
logDir.listFiles()?.forEach { file ->
|
||||
if (file.isFile && file.name.endsWith(".log")) {
|
||||
if (Date(file.lastModified()).before(thresholdDate)) file.delete()
|
||||
val thresholdDate = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -MAX_DAYS) }.time
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile && file.name.endsWith(".log") && Date(file.lastModified()).before(thresholdDate)) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {}
|
||||
|
|
@ -84,7 +125,6 @@ object LogManager {
|
|||
|
||||
fun zipLogs(context: Context): String? {
|
||||
val dir = logDir ?: return null
|
||||
if (!dir.exists() || dir.listFiles().isNullOrEmpty()) return null
|
||||
val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip")
|
||||
return try {
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
|
|
@ -97,48 +137,29 @@ object LogManager {
|
|||
}
|
||||
}
|
||||
zipFile.absolutePath
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Zip logs failed")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) { null }
|
||||
}
|
||||
|
||||
fun uploadLogs(context: Context, callback: (Boolean, String) -> Unit) {
|
||||
executor.execute {
|
||||
val zipPath = zipLogs(context)
|
||||
if (zipPath == null) {
|
||||
callback(false, "打包日志失败")
|
||||
return@execute
|
||||
}
|
||||
|
||||
val file = File(zipPath)
|
||||
val zipPath = zipLogs(context) ?: return@execute callback(false, "打包失败")
|
||||
val serverUrl = AppConfig.getServerUrl(context)
|
||||
val uploadUrl = "$serverUrl/api/attachment/upload"
|
||||
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQ5MDczNSwianRpIjoiMjYzNjljYzQtYzJiMy00YTg0LWFiYTMtZDU4NmNiZDRmMmM0IiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.AyuzNAe-R_GOBC65STH5qpHkmmUngX8rMBPI4CdIIhk"
|
||||
|
||||
val requestBody = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", file.name, file.asRequestBody("application/zip".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(uploadUrl)
|
||||
.header("Authorization", "Bearer $bearerToken")
|
||||
.post(requestBody)
|
||||
val body = MultipartBody.Builder().setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", File(zipPath).name, File(zipPath).asRequestBody("application/zip".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
val request = Request.Builder().url(uploadUrl).header("Authorization", "Bearer $bearerToken").post(body).build()
|
||||
try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) {
|
||||
callback(true, "上传成功")
|
||||
file.delete()
|
||||
} else {
|
||||
callback(false, "服务器返回错误: ${response.code}")
|
||||
}
|
||||
File(zipPath).delete()
|
||||
} else callback(false, "错误: ${response.code}")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
callback(false, "网络连接失败: ${e.message}")
|
||||
}
|
||||
} catch (e: IOException) { callback(false, "失败: ${e.message}") }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,18 +169,12 @@ object LogManager {
|
|||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
executor.execute {
|
||||
val logFile = File(logDir, "${fileNameFormat.format(Date())}.log")
|
||||
val priorityStr = when (priority) {
|
||||
2 -> "V"
|
||||
3 -> "D"
|
||||
4 -> "I"
|
||||
5 -> "W"
|
||||
6 -> "E"
|
||||
7 -> "A"
|
||||
else -> "U"
|
||||
}
|
||||
val entry = "${dateFormat.format(Date())} $priorityStr/[$tag]: $message\n${t?.stackTraceToString() ?: ""}\n"
|
||||
try { FileWriter(logFile, true).use { it.write(entry) } } catch (e: Exception) {}
|
||||
try {
|
||||
val logFile = File(logDir, "${fileNameFormat.format(Date())}.log")
|
||||
val priorityStr = when (priority) { 2->"V" 3->"D" 4->"I" 5->"W" 6->"E" 7->"A" else->"U" }
|
||||
val entry = "${dateFormat.format(Date())} $priorityStr/[$tag]: $message\n${t?.stackTraceToString() ?: ""}\n"
|
||||
FileWriter(logFile, true).use { it.write(entry) }
|
||||
} catch (e: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#10000000">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#FFFFFF" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F1F5F9">
|
||||
|
||||
<!-- 顶部导航栏 -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="#FFFFFF"
|
||||
android:elevation="2dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@android:drawable/ic_menu_revert"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="退出" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="调试控制台"
|
||||
android:textColor="#1E293B"
|
||||
android:textSize="17sp"
|
||||
android:fontFamily="sans-serif-medium" />
|
||||
</RelativeLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="核心模块测试"
|
||||
android:textColor="#64748B"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textAllCaps="true"
|
||||
android:letterSpacing="0.05"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
<!-- PTT 卡片 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/item_ptt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:background="@drawable/bg_developer_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_btn_speak_now"
|
||||
android:tint="#004A8F" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PTT 核心指令测试"
|
||||
android:textColor="#1E293B"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Native 引擎交互及语音对讲模拟"
|
||||
android:textColor="#94A3B8"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:tint="#CBD5E1" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 打印机卡片 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/item_printer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:background="@drawable/bg_developer_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_save"
|
||||
android:tint="#10B981" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="打印机串口测试"
|
||||
android:textColor="#1E293B"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="硬件指令调试及纸张状态检查"
|
||||
android:textColor="#94A3B8"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:tint="#CBD5E1" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 崩溃测试卡片 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/item_crash_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:background="@drawable/bg_developer_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_delete"
|
||||
android:tint="#EF4444" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="异常崩溃模拟"
|
||||
android:textColor="#1E293B"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="强制抛出运行时异常测试日志记录"
|
||||
android:textColor="#94A3B8"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="系统信息"
|
||||
android:textColor="#64748B"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_developer_item"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_debug_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="加载设备详细信息..."
|
||||
android:textColor="#475569"
|
||||
android:textSize="13sp"
|
||||
android:lineSpacingExtra="4dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Version 1.0.10 (Internal Build)\nStandalone Dispatch Terminal SDK"
|
||||
android:textColor="#94A3B8"
|
||||
android:textSize="11sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -130,29 +130,20 @@
|
|||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_ptt_example"
|
||||
android:id="@+id/btn_exit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/shape_button_login"
|
||||
android:text="PTT 示例"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_printer_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/shape_button_login"
|
||||
android:text="打印测试"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp" />
|
||||
android:background="@android:color/transparent"
|
||||
android:text="退出应用"
|
||||
android:textColor="#CCFFFFFF"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- 底部版权 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_copyright"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
|
|
@ -160,6 +151,9 @@
|
|||
android:layout_marginBottom="20dp"
|
||||
android:text="Copyright © 2020-2026 Telewave. All Rights Reserved."
|
||||
android:textColor="#88FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
android:textSize="12sp"
|
||||
android:padding="10dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue