feat: replace with native Android empty-shell UI (Main/Settings + stub ServerClient/WatchBridge)

This commit is contained in:
2026-09-01 18:49:15 +08:00
parent 18ec9fadc7
commit 0673ff77b7
29 changed files with 367 additions and 266 deletions
-7
View File
@@ -1,7 +0,0 @@
/.nyc_output
/coverage
/node_modules
/tests/fixtures
/sign
/dist
/build
+9 -15
View File
@@ -1,16 +1,10 @@
node_modules
build
dist
.DS_Store
Thumbs.db
*.log
*.iml
.gradle/
build/
local.properties
.idea/
.vscode/
.nyc_output
/coverage
/logs
*.iml
.DS_Store
*.log
captures/
.externalNativeBuild/
.cxx/
+21 -8
View File
@@ -1,13 +1,26 @@
## 快速上手
# DSHWatchPhone(手机端 / 原生 Android)
您可通过运行 `npm run start` 后,使用快应用引擎扫描二维码快速开发。
DSHWatch 手机客户端空壳。后续接入:
## 了解更多
- DSHWatch_Server(或 dsh-watch 插件直连)WebSocket。
- 小米穿戴 SDK(Wearable / MessageApi)作为手表 BLE 互联对端。
- 作为手表网关,把服务器状态/事件转发给 DSHWatch,并把手表指令转发回服务器。
你可以通过我们的[官方文档](https://doc.quickapp.cn/)熟悉和了解快应用。
## 当前为 UI 空壳
可以通过我们[快应用官方示例项目](https://github.com/quickappcn/sample),快速掌握
api 和组件的用法
- MainActivity:状态页(刷新 / 设置入口)。
- SettingsActivity:服务器地址 / userId / token 表单。
- WatchBridge.java / ServerClient.java:占位接口,未接入 SDK。
对我们有什么意见,建议或者 bug,可以在这里反馈
https://github.com/quickappcn/issues
## 构建
用 Android Studio 打开本目录(Gradle 会拉取 AGP + AndroidX)。
- compileSdk 34 / minSdk 26 / targetSdk 34
- Java 17
## 后续接入小米穿戴 SDK
- 从官方 demo(open-vela packages_apps / interconnect_image_demo 的 android_program)拿 libs/。
- 包名与签名需与 DSHWatch(手表 rpk)一致:com.dshwatch.app。
- 在 ServerClient 里接 WebSocket,在 WatchBridge 里接 MessageApi。
+29
View File
@@ -0,0 +1,29 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.dshwatch.app'
compileSdk 34
defaultConfig {
applicationId "com.dshwatch.app"
minSdk 26
targetSdk 34
versionCode 1
versionName "0.1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
+1
View File
@@ -0,0 +1 @@
# Add project specific ProGuard rules here.
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/Theme.DSHWatch">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:exported="false" />
</application>
</manifest>
@@ -0,0 +1,38 @@
package com.dshwatch.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView status = findViewById(R.id.status_text);
status.setText(getString(R.string.status_disconnected));
Button settings = findViewById(R.id.btn_settings);
settings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
}
});
Button refresh = findViewById(R.id.btn_refresh);
refresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 空壳:后续调用 ServerClient 拉取 Harness 状态
status.setText(R.string.status_disconnected);
}
});
}
}
@@ -0,0 +1,31 @@
package com.dshwatch.app;
/**
* Harness 服务器客户端(空壳)。
* 后续接入 WebSocket(OkHttp ws),通过 DSHWatch_Server 或直连 dsh-watch 插件,
* 实现 harness.status / session.prompt / session.list 等 RPC。
*/
public class ServerClient {
private String serverUrl;
private String userId;
private String token;
public ServerClient(String serverUrl, String userId, String token) {
this.serverUrl = serverUrl;
this.userId = userId;
this.token = token;
}
public void connect() {
// 空壳:TODO 实现 WebSocket 握手 + 请求/响应 + 事件回调
}
public void sendRequest(String method, String paramsJson, Callback cb) {
// 空壳:TODO
}
public interface Callback {
void onResult(String json);
void onError(String message);
}
}
@@ -0,0 +1,35 @@
package com.dshwatch.app;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class SettingsActivity extends AppCompatActivity {
private EditText serverUrl;
private EditText userId;
private EditText token;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
serverUrl = findViewById(R.id.input_server);
userId = findViewById(R.id.input_user);
token = findViewById(R.id.input_token);
Button save = findViewById(R.id.btn_save);
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 空壳:后续持久化到 SharedPreferences / 安全存储
Toast.makeText(SettingsActivity.this, "保存(空壳): " + serverUrl.getText(), Toast.LENGTH_SHORT).show();
}
});
}
}
@@ -0,0 +1,15 @@
package com.dshwatch.app;
/**
* 手表 BLE 互联桥(空壳)。
* 后续接入小米穿戴 SDK:Wearable / MessageApi / sendMessage / addListener,
* 用于把手表(DSHWatch) 与手机端双向转发。
*/
public interface WatchBridge {
/** 连接手表(小米穿戴服务) */
void connect();
/** 发送消息给手表 */
void sendToWatch(String json);
/** 接收来自手表的消息 */
String onWatchMessage(String json);
}
+48
View File
@@ -0,0 +1,48 @@
<?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:padding="24dp"
android:background="@color/deep_blue">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/status_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/status_disconnected"
android:textColor="@color/white"
android:textSize="16sp" />
<Button
android:id="@+id/btn_refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/btn_refresh" />
<Button
android:id="@+id/btn_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/btn_settings" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/shell_hint"
android:textColor="@color/light_blue"
android:textSize="13sp" />
</LinearLayout>
@@ -0,0 +1,69 @@
<?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:padding="24dp"
android:background="@color/deep_blue">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings_title"
android:textColor="@color/white"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/label_server"
android:textColor="@color/light_blue" />
<EditText
android:id="@+id/input_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_server"
android:textColor="@color/white"
android:textColorHint="@color/light_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/label_user"
android:textColor="@color/light_blue" />
<EditText
android:id="@+id/input_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_user"
android:textColor="@color/white"
android:textColorHint="@color/light_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/label_token"
android:textColor="@color/light_blue" />
<EditText
android:id="@+id/input_token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_token"
android:inputType="textPassword"
android:textColor="@color/white"
android:textColorHint="@color/light_blue" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/btn_save" />
</LinearLayout>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="deep_blue">#0B1220</color>
<color name="white">#FFFFFF</color>
<color name="light_blue">#B7C8E8</color>
</resources>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">DSHWatch Phone</string>
<string name="status_disconnected">未连接(空壳)</string>
<string name="btn_refresh">刷新状态</string>
<string name="btn_settings">设置</string>
<string name="settings_title">设置</string>
<string name="label_server">服务器地址</string>
<string name="hint_server">ws://host:port</string>
<string name="label_user">用户ID</string>
<string name="hint_user">userId</string>
<string name="label_token">Token</string>
<string name="hint_token">token</string>
<string name="btn_save">保存(空壳)</string>
<string name="shell_hint">空壳 UI:后续接入 DSHWatch_Server + 小米穿戴 SDK(BLE 网关)。</string>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.DSHWatch" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">#33B4FF</item>
<item name="colorPrimaryDark">#0B1220</item>
<item name="colorAccent">#33B4FF</item>
<item name="android:windowBackground">@color/deep_blue</item>
</style>
</resources>
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'com.android.application' version '8.5.2' apply false
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
View File
-18
View File
@@ -1,18 +0,0 @@
{
"name": "DSHWatchPhone",
"version": "1.0.0",
"description": "",
"engines": {
"node": ">=8.10"
},
"scripts": {
"start": "aiot start --watch",
"build": "aiot build",
"release": "aiot release",
"lint": "eslint --format codeframe --fix --ext .ux,.js src/"
},
"devDependencies": {
"@aiot-toolkit/jsc": "^1.0.3",
"aiot-toolkit": "^2.0.5"
}
}
+16
View File
@@ -0,0 +1,16 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "DSHWatchPhone"
include ':app'
-24
View File
@@ -1,24 +0,0 @@
<template>
<!-- template里只能有一个根节点 -->
<div class="demo-page">
<text>{{text}}</text>
<text>{{des}}</text>
</div>
</template>
<script>
export default {
private: {
text: '欢迎使用快应用卡片',
des: '卡片是一种轻量级的快应用,快应用卡片使快应用能够在其他app和系统里提供可扩展的app功能'
}
}
</script>
<style>
.demo-page {
flex: 1;
flex-direction: column;
align-items: center;
}
</style>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

-48
View File
@@ -1,48 +0,0 @@
<template>
<!-- template里只能有一个根节点 -->
<div class="demo-page">
<text class="title">欢迎打开{{title}}</text>
<!-- 点击跳转详情页 -->
<input class="btn" type="button" value="跳转到详情页" onclick="routeDetail" />
</div>
</template>
<script>
import router from '@system.router'
export default {
// 页面级组件的数据模型,影响传入数据的覆盖机制:private内定义的属性不允许被覆盖
private: {
title: '示例页面'
},
routeDetail () {
// 跳转到应用内的某个页面,router用法详见:文档->接口->页面路由
router.push ({
uri: '/DemoDetail'
})
}
}
</script>
<style>
.demo-page {
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
font-size: 40px;
text-align: center;
}
.btn {
width: 550px;
height: 86px;
margin-top: 75px;
border-radius: 43px;
background-color: #09ba07;
font-size: 30px;
color: #ffffff;
}
</style>
-27
View File
@@ -1,27 +0,0 @@
<template>
<!-- template里只能有一个根节点 -->
<div class="demo-page">
<text class="title">{{text}}</text>
</div>
</template>
<script>
export default {
private: {
text: '欢迎打开详情页'
}
}
</script>
<style>
.demo-page {
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
font-size: 40px;
text-align: center;
}
</style>
-14
View File
@@ -1,14 +0,0 @@
<script>
/**
* @file 应用级别的配置,供所有页面公用
* 可在app.ux导出公用变量
* 其他src目录下的ux文件使用this.$app.$def.<paramName>即可调用此处导出的变量
* 文档地址: https://doc.quickapp.cn/framework/source-file.html#appux
*/
// 引入全局函数定义文件global.js
import './global'
export default {}
</script>
-1
View File
@@ -1 +0,0 @@
{}
-34
View File
@@ -1,34 +0,0 @@
/**
* @file 全局能力的配置与获取
* 文档地址:https://doc.quickapp.cn/tutorial/framework/optimization-skills.html#%E4%BD%BF%E7%94%A8-globaljs
*/
function getGlobalRef() {
return Object.getPrototypeOf(global) || global
}
const quickappGlobal = getGlobalRef()
/**
* 设置全局(被APP与Page共享)数据;
* @param key {string}
* @param val {*}
*/
function setGlobalData(key, val) {
quickappGlobal[key] = val
}
/**
* 获取全局(被APP与Page共享)数据;
* @param key {string}
* @return {*}
*/
function getGlobalData(key) {
return quickappGlobal[key]
}
// 两个方法默认定义在全局
setGlobalData('setGlobalData', setGlobalData)
setGlobalData('getGlobalData', getGlobalData)
export { setGlobalData, getGlobalData }
-66
View File
@@ -1,66 +0,0 @@
{
"package": "com.application.demo",
"name": "DSHWatchPhone",
"versionName": "1.0.0",
"versionCode": 1,
"minPlatformVersion": 1070,
"icon": "/Common/logo.png",
"deviceTypeList": [
"phone"
],
"features": [
{
"name": "system.prompt"
},
{
"name": "system.router"
},
{
"name": "system.shortcut"
}
],
"permissions": [
{
"origin": "*"
}
],
"config": {
"logLevel": "log"
},
"router": {
"entry": "Demo",
"pages": {
"Demo": {
"component": "index"
},
"DemoDetail": {
"component": "index"
}
},
"widgets": {
"CardDemo": {
"name": "CardDemo",
"description": "快应用卡片展示",
"component": "index",
"path": "/CardDemo",
"features": []
}
}
},
"display": {
"titleBarBackgroundColor": "#f2f2f2",
"titleBarTextColor": "#414141",
"menu": false,
"menuBarData": {
"menuBar": true
},
"pages": {
"Demo": {
"titleBarText": "示例页"
},
"DemoDetail": {
"titleBarText": "详情页"
}
}
}
}
-4
View File
@@ -1,4 +0,0 @@
{
"doc": "https://doc.quickapp.cn/framework/sitemap.html",
"rules": [{ "rule": "enable", "page": "*" }]
}