feat: phone client as WebView shell of Harness web app + settings (webUrl / watch link / Hermes); menu
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
package com.dshwatch.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -14,24 +21,45 @@ import org.json.JSONObject;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private TextView status;
|
||||
private TextView balanceView;
|
||||
private WebView webView;
|
||||
private View emptyHint;
|
||||
private ServerClient serverClient;
|
||||
private WatchBridge watchBridge;
|
||||
private boolean balancePending = false;
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
status = findViewById(R.id.status_text);
|
||||
balanceView = findViewById(R.id.balance_text);
|
||||
webView = findViewById(R.id.webview);
|
||||
emptyHint = findViewById(R.id.empty_hint);
|
||||
|
||||
// 服务器客户端
|
||||
// WebView:加载 Harness 网页壳(框子常驻,数据由插件/网关填入)
|
||||
WebSettings ws = webView.getSettings();
|
||||
ws.setJavaScriptEnabled(true);
|
||||
ws.setDomStorageEnabled(true);
|
||||
ws.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
ws.setAllowFileAccess(true);
|
||||
ws.setMediaPlaybackRequiresUserGesture(false);
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
emptyHint.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
loadWeb();
|
||||
|
||||
// 服务器客户端:连 Harness 插件(把 Harness 数据串过来的通道)
|
||||
serverClient = new ServerClient(new ServerClient.Listener() {
|
||||
@Override
|
||||
public void onConnectionChanged(boolean connected) {
|
||||
runOnUiThread(() -> status.setText(connected ? "服务器已连接" : "服务器断开"));
|
||||
runOnUiThread(() -> status.setText(connected ? "已连接 Harness" : "未连接 Harness"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,10 +101,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
double total = (infos != null && infos.length() > 0 && infos.optJSONObject(0) != null)
|
||||
? infos.optJSONObject(0).optDouble("total_balance", 0.0) : 0.0;
|
||||
String balance = String.format("%.2f", total);
|
||||
runOnUiThread(() -> {
|
||||
status.setText("余额 ¥ " + balance);
|
||||
balanceView.setText("DeepSeek 余额 ¥" + balance);
|
||||
});
|
||||
runOnUiThread(() -> status.setText("DeepSeek 余额 ¥" + balance));
|
||||
JSONObject payload = new JSONObject();
|
||||
try { payload.put("balance", balance); } catch (Exception e) {}
|
||||
JSONObject msg = new JSONObject();
|
||||
@@ -93,7 +118,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// 手表互联桥
|
||||
// 手表互联桥(BLE 网关,后续完善“手机连接手表”)
|
||||
watchBridge = new WatchBridge(this, new WatchBridge.Listener() {
|
||||
@Override
|
||||
public void onWatchConnected(boolean connected, String nodeId) {
|
||||
@@ -134,51 +159,66 @@ public class MainActivity extends AppCompatActivity {
|
||||
runOnUiThread(() -> status.setText("WatchBridge: " + message));
|
||||
}
|
||||
});
|
||||
|
||||
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 sessions = findViewById(R.id.btn_sessions);
|
||||
sessions.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainActivity.this, SessionListActivity.class));
|
||||
private void loadWeb() {
|
||||
String url = SettingsStore.webUrl(this);
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
emptyHint.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
Button events = findViewById(R.id.btn_events);
|
||||
events.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainActivity.this, EventLogActivity.class));
|
||||
emptyHint.setVisibility(View.GONE);
|
||||
webView.loadUrl(url.trim());
|
||||
}
|
||||
});
|
||||
|
||||
Button command = findViewById(R.id.btn_command);
|
||||
command.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainActivity.this, CommandActivity.class));
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Button refresh = findViewById(R.id.btn_refresh);
|
||||
refresh.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_refresh) {
|
||||
status.setText("连接中...");
|
||||
String serverUrl = SettingsStore.serverUrl(MainActivity.this);
|
||||
String userId = SettingsStore.userId(MainActivity.this);
|
||||
String token = SettingsStore.token(MainActivity.this);
|
||||
String serverUrl = SettingsStore.serverUrl(this);
|
||||
String userId = SettingsStore.userId(this);
|
||||
String token = SettingsStore.token(this);
|
||||
serverClient.connect(serverUrl, userId, token);
|
||||
watchBridge.connect();
|
||||
loadWeb();
|
||||
return true;
|
||||
} else if (id == R.id.action_watch) {
|
||||
watchBridge.connect();
|
||||
Toast.makeText(this, "正在连接手表…", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
} else if (id == R.id.action_hermes) {
|
||||
Toast.makeText(this, "Hermès 链接(待接入)", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
} else if (id == R.id.action_sessions) {
|
||||
startActivity(new Intent(this, SessionListActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_events) {
|
||||
startActivity(new Intent(this, EventLogActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_command) {
|
||||
startActivity(new Intent(this, CommandActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_settings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView != null && webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -13,6 +14,10 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
private EditText serverUrl;
|
||||
private EditText userId;
|
||||
private EditText token;
|
||||
private EditText web;
|
||||
private TextView watchStatus;
|
||||
private TextView hermesStatus;
|
||||
private WatchBridge watchBridge;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -22,6 +27,14 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
serverUrl = findViewById(R.id.input_server);
|
||||
userId = findViewById(R.id.input_user);
|
||||
token = findViewById(R.id.input_token);
|
||||
web = findViewById(R.id.input_web);
|
||||
watchStatus = findViewById(R.id.watch_status);
|
||||
hermesStatus = findViewById(R.id.hermes_status);
|
||||
|
||||
serverUrl.setText(SettingsStore.serverUrl(this));
|
||||
userId.setText(SettingsStore.userId(this));
|
||||
token.setText(SettingsStore.token(this));
|
||||
web.setText(SettingsStore.webUrl(this));
|
||||
|
||||
Button save = findViewById(R.id.btn_save);
|
||||
save.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -32,8 +45,44 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
serverUrl.getText().toString().trim(),
|
||||
userId.getText().toString().trim(),
|
||||
token.getText().toString().trim());
|
||||
SettingsStore.saveWebUrl(SettingsActivity.this, web.getText().toString().trim());
|
||||
Toast.makeText(SettingsActivity.this, "已保存", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
Button watchConnect = findViewById(R.id.btn_watch_connect);
|
||||
watchConnect.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
watchStatus.setText("正在连接手表…");
|
||||
watchBridge = new WatchBridge(SettingsActivity.this, new WatchBridge.Listener() {
|
||||
@Override
|
||||
public void onWatchConnected(boolean connected, String nodeId) {
|
||||
runOnUiThread(() -> watchStatus.setText(connected ? "已连接: " + nodeId : "未找到手表"));
|
||||
}
|
||||
@Override
|
||||
public void onWatchMessage(String json) { }
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
runOnUiThread(() -> watchStatus.setText("错误: " + message));
|
||||
}
|
||||
});
|
||||
watchBridge.connect();
|
||||
}
|
||||
});
|
||||
|
||||
Button hermes = findViewById(R.id.btn_hermes);
|
||||
hermes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
hermesStatus.setText("Hermès 链接(待接入)");
|
||||
Toast.makeText(SettingsActivity.this, "Hermès 链接待接入", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public class SettingsStore {
|
||||
private static final String KEY_SERVER = "serverUrl";
|
||||
private static final String KEY_USER = "userId";
|
||||
private static final String KEY_TOKEN = "token";
|
||||
private static final String KEY_WEB = "webUrl";
|
||||
|
||||
public static void save(Context c, String serverUrl, String userId, String token) {
|
||||
SharedPreferences p = c.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
@@ -29,4 +30,12 @@ public class SettingsStore {
|
||||
public static String token(Context c) {
|
||||
return c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_TOKEN, "change-me");
|
||||
}
|
||||
|
||||
public static String webUrl(Context c) {
|
||||
return c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_WEB, "http://10.0.2.2:43120");
|
||||
}
|
||||
|
||||
public static void saveWebUrl(Context c, String url) {
|
||||
c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putString(KEY_WEB, url == null ? "" : url).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
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" />
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@android:color/transparent"
|
||||
app:title="DeepSeek Harness"
|
||||
app:titleTextColor="@color/white"
|
||||
app:titleTextAppearance="@style/TextAppearance.MaterialComponents.Headline6">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/status_disconnected"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/balance_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="DeepSeek 余额 ¥--"
|
||||
android:textColor="#33B4FF"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/btn_sessions" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_events"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/btn_events" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_command"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/btn_command" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/shell_hint"
|
||||
android:layout_gravity="end"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="未连接"
|
||||
android:textColor="@color/light_blue"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:padding="24dp"
|
||||
android:text="请在设置中填写 Harness 网页地址\n(例如 http://10.0.2.2:43120)\n\n点右上角设置可配置"
|
||||
android:textColor="@color/light_blue"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -60,6 +60,75 @@
|
||||
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="Harness 网页地址(串流壳,如 http://10.0.2.2:43120)"
|
||||
android:textColor="@color/light_blue" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/input_web"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="http://10.0.2.2:43120"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="@color/light_blue" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="链接"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="⌚ 手表链接"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/watch_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="未连接"
|
||||
android:textColor="@color/light_blue"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_watch_connect"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="连接手表" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="🔗 Hermès 链接"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hermes_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="未配置"
|
||||
android:textColor="@color/light_blue"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_hermes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="连接 Hermès" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_save"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_refresh" android:title="刷新" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_watch" android:title="手表链接" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_hermes" android:title="Hermès 链接" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_sessions" android:title="会话列表" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_events" android:title="事件流" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_command" android:title="命令" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_settings" android:title="设置" app:showAsAction="always" />
|
||||
</menu>
|
||||
Reference in New Issue
Block a user