diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba5e82c --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Temi 机器人应用开发与预览指南 + +本项目是一个基于 Temi SDK 的 Android 应用程序,包含主界面和网络 IP 设置功能。 + +## 1. 预览与运行环境 + +### 推荐工具:Android Studio +由于 Android 应用依赖于复杂的构建系统 (Gradle) 和特定的运行时环境 (Android OS),**无法像网页那样直接通过浏览器或简单的编辑器插件预览**。 + +最标准的开发和预览工具是 **Android Studio** (官方 IDE)。 + +### Trae 的角色 +Trae 非常适合编写代码、管理项目结构和进行逻辑开发。但为了查看应用的实际运行效果(UI 和交互),你需要将代码编译成 APK 并安装到设备上,这通常需要 Android Studio 的配合。 + +--- + +## 2. 如何在另一台电脑上运行 + +### 步骤 1: 安装 Android Studio +请访问 [Android Studio 官网](https://developer.android.com/studio) 下载并安装最新版本。 + +### 步骤 2: 导入项目 +1. 打开 Android Studio。 +2. 选择 **Open** (打开项目)。 +3. 导航到本项目所在的文件夹 `lzwcai-terminal-temi` 并点击 OK。 +4. 等待 Android Studio 自动同步 Gradle 依赖(这可能需要几分钟,取决于网络)。 + +### 步骤 3: 运行应用 (两种方式) + +#### 方式 A: 连接 Temi 机器人 (强烈推荐) +Temi SDK 的很多功能(如语音、导航、跟随)依赖于机器人的硬件传感器。 +1. 确保 Temi 机器人已开启 **开发者模式** (设置 -> 通用 -> 开发者选项)。 +2. 使用 USB 线(通常是 USB-A 转 USB-A 或通过背部接口)将电脑连接到 Temi。 +3. 或者使用 **ADB over Wi-Fi** (确保电脑和 Temi 在同一 Wi-Fi 下)。 +4. 在 Android Studio 顶部工具栏选择连接的设备,点击 **Run** (绿色三角形按钮)。 + +#### 方式 B: 使用 Android 模拟器 (仅限 UI 预览) +如果你身边没有机器人,可以使用模拟器预览界面布局,但会报错或功能受限。 +1. 在 Android Studio 中点击 **Device Manager** 创建一个虚拟设备 (推荐 Pixel 系列,API 30+)。 +2. 运行模拟器。 +3. **注意**:由于模拟器没有 Temi 的系统服务,`Robot.getInstance()` 可能会导致崩溃或返回错误。 + - *提示*:为了在模拟器上查看 UI,你可能需要暂时注释掉 `MainActivity.kt` 中调用 `Robot` 的代码。 + +--- + +## 3. 项目结构说明 + +- **主界面**: `app/src/main/java/.../MainActivity.kt` + - 包含日志显示和跳转设置的按钮。 + - 初始化 Robot SDK。 +- **设置界面**: `app/src/main/java/.../SettingsActivity.kt` + - 用于设置和保存网络 IP 地址。 +- **布局文件**: + - `app/src/main/res/layout/activity_main.xml` (主页布局) + - `app/src/main/res/layout/activity_settings.xml` (设置页布局) + +## 4. 常见问题 + +**Q: 为什么在模拟器上闪退?** +A: 因为应用启动时会调用 `Robot.getInstance()`,而普通模拟器没有 Temi 的底层服务。 + +**Q: Trae 可以装插件预览吗?** +A: 目前没有插件能直接在 Trae 内部完美模拟 Android 环境。建议使用 Trae 写代码,配合 Android Studio 或真机进行调试预览。 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ffd1beb..85c88a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,10 +33,13 @@ android { kotlinOptions { jvmTarget = "11" } + buildFeatures { + viewBinding = true + } } dependencies { - + implementation("com.robotemi:sdk:1.137.1") implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index faa539f..8eefd30 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,11 @@ + + + + + + tools:targetApi="31"> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt b/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt index 544f1d7..cda95d1 100644 --- a/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt @@ -1,2 +1,70 @@ package com.example.lzwcai_terminal_temi +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.method.ScrollingMovementMethod +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) + + // 隐藏软键盘 + 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 + 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... + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lzwcai_terminal_temi/SettingsActivity.kt b/app/src/main/java/com/example/lzwcai_terminal_temi/SettingsActivity.kt new file mode 100644 index 0000000..88f64f3 --- /dev/null +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/SettingsActivity.kt @@ -0,0 +1,34 @@ +package com.example.lzwcai_terminal_temi + +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.example.lzwcai_terminal_temi.databinding.ActivitySettingsBinding + +class SettingsActivity : AppCompatActivity() { + + private lateinit var binding: ActivitySettingsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySettingsBinding.inflate(layoutInflater) + setContentView(binding.root) + + val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE) + val savedIp = prefs.getString("network_ip", "") + binding.etIpAddress.setText(savedIp) + + binding.btnSave.setOnClickListener { + 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() + finish() + } else { + Toast.makeText(this, "Please enter a valid IP", Toast.LENGTH_SHORT).show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..c610d3e --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,31 @@ + + + + + +