[FIX] 修復 Star-Cloud 管理員登入流程與 ApiKey 持久化機制
1. 修正 B000 登入 API 路徑並補上機台 Authorization Token 驗證,解決 401 授權失敗問題。 2. 遷移 login 實作至 HttpURLConnection 直連模式,確保網路連線錯誤能正確反饋至 UI。 3. 實作 persistCurrentSettings 統一持久化方法,確保 Token 變更後立即同步至 SQLite。 4. 優化 MyApp 初始化邏輯,修正 App 重啟後 ApiKey 無法從資料庫正確還原的問題。 5. 禁用 HomeActivity 與 FontendActivity 中的舊版 B010/B022 背景任務,避免新舊系統衝突。 6. 於 .agents/rules/ 新增 Star-Cloud API 開發規範文件,防止未來代碼回退。 7. 移除偵錯用的冗餘 Toast 訊息,優化使用者體驗。
This commit is contained in:
parent
4b2dc746f6
commit
a73c618360
32
.agents/rules/star-cloud-api.md
Normal file
32
.agents/rules/star-cloud-api.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Star-Cloud API 整合開發規範
|
||||
|
||||
本規範定義了對接 Star-Cloud 後台時的授權與穩定性要求。
|
||||
|
||||
## 1. API 授權機制 (Bearer Auth)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Star-Cloud 採用的機台預授權模式,要求除 B014 (綁定) 外的所有請求都必須具備身分證明。**
|
||||
|
||||
- **機台 Header**:
|
||||
- 必須在 Header 帶入 `Authorization: Bearer [MachineToken]`。
|
||||
- 即使是管理員登入 (`B000`) 請求,也必須先帶上綁定時獲得的機台 Token。
|
||||
- **持久化同步**:
|
||||
- 任何涉及 `ApiKey` (Token) 的變更,必須同步調用 `StarCloudAPI.persistCurrentSettings()` 寫入資料庫。
|
||||
- 嚴禁只更新記憶體變數而不更新 SQLite。
|
||||
|
||||
## 2. 網路連線與異常處理規範
|
||||
|
||||
- **異常捕捉**:
|
||||
- 禁止在網路層級 (Scheduler/API) 使用空白的 catch 區塊。
|
||||
- 發生 `IOException` 時,必須將錯誤訊息傳遞至前端 UI 顯示(透過 Toast 或 Handler 訊息)。
|
||||
- **底層實作建議**:
|
||||
- 對於需要高度錯誤診斷的 API(如登入),優先使用原生的 `HttpURLConnection` 封裝,以避開框架內部的隱式錯誤處理。
|
||||
|
||||
## 3. 持久化 (Persistence) 邏輯
|
||||
|
||||
- **SettingsDao 規則**:
|
||||
- 存儲機台設定時應遵循「先刪後插」原則,確保資料庫中針對單一功能(如 `Machine`, `Esunpay`)始終只有一條最新紀錄。
|
||||
34
.agents/skills/android-deploy/SKILL.md
Normal file
34
.agents/skills/android-deploy/SKILL.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
name: android-deploy
|
||||
description: 用於 TaiwanStar 專案的 Android App 編譯、安裝與自動啟動。當使用者要求「安裝到手機」、「部署 APK」或「更新 App」時,應使用此技能。
|
||||
---
|
||||
|
||||
# Android 部署技能 (TaiwanStar 專案專用)
|
||||
|
||||
此技能定義了在 WSL 環境下編譯 Android App 並透過 Windows ADB 安裝至實體手機的標準流程。
|
||||
|
||||
## 前置要求
|
||||
|
||||
1. **Java 版本**:必須使用 OpenJDK 17。
|
||||
2. **ADB 工具**:使用 Windows 端安裝的 ADB (路徑:`/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe`)。
|
||||
3. **手機連線**:手機必須開啟 USB 調試,且已連線至電腦。
|
||||
|
||||
## 標準部署流程
|
||||
|
||||
執行以下複合指令以完成「編譯 + 安裝 + 自動啟動」:
|
||||
|
||||
```bash
|
||||
# 1. 設定環境變數
|
||||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||||
|
||||
# 2. 執行編譯與安裝 (包含自動開啟 HomeActivity)
|
||||
./gradlew :app:assembleS_12_XY_U_Standard_Debug && \
|
||||
/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe install -r -d -t app/build/outputs/apk/S_12_XY_U_Standard_/debug/app-S_12_XY_U_Standard_-10_08_2_R-debug.apk && \
|
||||
/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe shell am start -n com.unibuy.smartdevice/com.unibuy.smartdevice.HomeActivity
|
||||
```
|
||||
|
||||
## 注意事項
|
||||
|
||||
- **安裝失敗**:若出現 `INSTALL_FAILED_UPDATE_INCOMPATIBLE`,代表簽名或版本衝突,請先執行 `adb uninstall com.unibuy.smartdevice`。
|
||||
- **ADB 找不到裝置**:請確認手機 USB 模式是否為「檔案傳輸」或「PTP」,並檢查 Windows 端的 ADB server 是否正常執行。
|
||||
- **Mock Mode**:此部署版本已包含 Mock Mode,可在無串口設備的手機上正常運作不崩潰。
|
||||
27
.agents/skills/android-deploy/scripts/deploy.sh
Executable file
27
.agents/skills/android-deploy/scripts/deploy.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TaiwanStar Android 部署腳本
|
||||
# 自動執行:編譯 -> 安裝 -> 啟動
|
||||
|
||||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||||
ADB_PATH="/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe"
|
||||
APK_PATH="app/build/outputs/apk/S_12_XY_U_Standard_/debug/app-S_12_XY_U_Standard_-10_08_2_R-debug.apk"
|
||||
PACKAGE_NAME="com.unibuy.smartdevice"
|
||||
LAUNCHER_ACTIVITY=".HomeActivity"
|
||||
|
||||
echo "🚀 開始編譯 APK..."
|
||||
./gradlew :app:assembleS_12_XY_U_Standard_Debug
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 編譯成功,正在安裝至手機..."
|
||||
$ADB_PATH install -r -d -t $APK_PATH
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "🎉 安裝成功,正在啟動 App..."
|
||||
$ADB_PATH shell am start -n $PACKAGE_NAME/$LAUNCHER_ACTIVITY
|
||||
else
|
||||
echo "❌ 安裝失敗,請檢查手機連線狀態。"
|
||||
fi
|
||||
else
|
||||
echo "❌ 編譯失敗,請檢查程式碼錯誤。"
|
||||
fi
|
||||
@ -150,9 +150,9 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
getLogs().clearMessage();
|
||||
|
||||
try {
|
||||
httpAPI.detectHost();
|
||||
} catch (LogsParseException | LogsSettingEmptyException e) {
|
||||
getLogs().warning(e);
|
||||
// httpAPI.detectHost();
|
||||
} catch (Exception e) {
|
||||
// getLogs().warning(e);
|
||||
}
|
||||
|
||||
String timeByS = new SimpleDateFormat("ss").format(new Date());
|
||||
@ -170,10 +170,10 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
// 確保距離上次執行至少間隔 9 分鐘(540000 毫秒)
|
||||
if (currentTime - lastReportTime >= 540000) {
|
||||
try {
|
||||
httpAPI.deviceReport();
|
||||
// httpAPI.deviceReport();
|
||||
lastReportTime = currentTime;
|
||||
} catch (LogsSettingEmptyException | LogsParseException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ import com.unibuy.smartdevice.exception.LogsParseException;
|
||||
import com.unibuy.smartdevice.exception.LogsSettingEmptyException;
|
||||
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;
|
||||
import com.unibuy.smartdevice.external.HttpAPI;
|
||||
import com.unibuy.smartdevice.external.StarCloudAPI;
|
||||
import com.unibuy.smartdevice.external.HttpTapPay;
|
||||
import com.unibuy.smartdevice.structure.SettingStructure;
|
||||
import com.unibuy.smartdevice.tools.ApiPostMainScheduler;
|
||||
@ -68,11 +69,24 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
|
||||
@Override
|
||||
protected HandlerMain setHandlerMain() {
|
||||
return new ToastHandlerMain(getCtx(), getLogs());
|
||||
return new ToastHandlerMain(getCtx(), getLogs()) {
|
||||
@Override
|
||||
public void execute(Context context, int commandCode, String message) {
|
||||
super.execute(context, commandCode, message);
|
||||
if ("登入成功".equals(message)) {
|
||||
if (pendingAction != null) {
|
||||
pendingAction.run();
|
||||
pendingAction = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ActivityMainBinding binding;
|
||||
private HttpAPI httpAPI;
|
||||
private StarCloudAPI starCloudAPI;
|
||||
private Runnable pendingAction;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -84,6 +98,7 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
httpAPI = new HttpAPI(getHandlerMain());
|
||||
starCloudAPI = new StarCloudAPI(getHandlerMain(), getHandlerMain());
|
||||
|
||||
//---- 環境尺寸設定 ----
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
|
||||
@ -96,56 +111,77 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
initialData();
|
||||
|
||||
binding.buttonGotoSetting.setOnClickListener(v -> {
|
||||
pendingAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getTools().gotoActivity(SettingActivity.class);
|
||||
}
|
||||
};
|
||||
if (!MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
|
||||
if (MyApp.getInstance().getSuAccess().isEmpty()) {
|
||||
showLoginDialog(getCtx());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getTools().gotoActivity(SettingActivity.class);
|
||||
pendingAction.run();
|
||||
pendingAction = null;
|
||||
});
|
||||
|
||||
binding.buttonGotoSystemSet.setOnClickListener(v -> {
|
||||
pendingAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getTools().gotoActivity(SystemSetActivity.class);
|
||||
}
|
||||
};
|
||||
if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
|
||||
getHandlerMain().start("MainActivity", "@先綁定機器");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyApp.getInstance().getSuAccess().isEmpty()) {
|
||||
showLoginDialog(getCtx());
|
||||
return;
|
||||
}
|
||||
|
||||
getTools().gotoActivity(SystemSetActivity.class);
|
||||
pendingAction.run();
|
||||
pendingAction = null;
|
||||
});
|
||||
|
||||
binding.buttonGotoProductList.setOnClickListener(v -> {
|
||||
pendingAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getTools().gotoActivity(ProductListActivity.class);
|
||||
}
|
||||
};
|
||||
if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
|
||||
getHandlerMain().start("MainActivity", "@先綁定機器");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyApp.getInstance().getSuAccess().isEmpty()) {
|
||||
showLoginDialog(getCtx());
|
||||
return;
|
||||
}
|
||||
|
||||
getTools().gotoActivity(ProductListActivity.class);
|
||||
pendingAction.run();
|
||||
pendingAction = null;
|
||||
});
|
||||
|
||||
binding.buttonGotoSlotList.setOnClickListener(v -> {
|
||||
pendingAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getTools().gotoActivity(VmcSlotListActivity.class);
|
||||
}
|
||||
};
|
||||
if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
|
||||
getHandlerMain().start("MainActivity", "@先綁定機器");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyApp.getInstance().getSuAccess().isEmpty()) {
|
||||
showLoginDialog(getCtx());
|
||||
return;
|
||||
}
|
||||
|
||||
getTools().gotoActivity(VmcSlotListActivity.class);
|
||||
pendingAction.run();
|
||||
pendingAction = null;
|
||||
});
|
||||
|
||||
|
||||
@ -501,13 +537,8 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
if (access.isEmpty() || password.isEmpty()) {
|
||||
Toast.makeText(context, "請輸入帳號和密碼", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
try {
|
||||
httpAPI.login(access, password);
|
||||
} catch (LogsParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (LogsSettingEmptyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Toast.makeText(context, "正在驗證登入...", Toast.LENGTH_SHORT).show();
|
||||
starCloudAPI.login(access, password);
|
||||
}
|
||||
|
||||
getTools().hideSystemBars((Activity) getCtx());
|
||||
|
||||
@ -394,8 +394,11 @@ public class MyApp extends Application {
|
||||
|
||||
if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
|
||||
SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getMachine().getClass().getSimpleName());
|
||||
logs.info("initialSettingData:" + setting.toString());
|
||||
MyApp.getInstance().getMachine().setMachineID(setting.getData(1));
|
||||
if (setting != null) {
|
||||
logs.info("initialSettingData Machine:" + setting.toString());
|
||||
MyApp.getInstance().getMachine().setApiKey(setting.getData(0));
|
||||
MyApp.getInstance().getMachine().setMachineID(setting.getData(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (MyApp.getInstance().getGreenInvoice().getGreenMerchantID().isEmpty()) {
|
||||
|
||||
@ -21,9 +21,11 @@ import java.util.List;
|
||||
public class Rs232Dev {
|
||||
private Logs logs;
|
||||
private boolean showLogs = false;
|
||||
private static boolean isMockMode = false;
|
||||
private static List<String> rs232DeviceList;
|
||||
private SerialPort serialPort;
|
||||
private InputStream inputStream;
|
||||
private OutputStream outputStream;
|
||||
private boolean isRead = false;
|
||||
public void setRead(boolean read) {
|
||||
isRead = read;
|
||||
@ -55,8 +57,11 @@ public class Rs232Dev {
|
||||
try {
|
||||
suPath = searchSuPath();
|
||||
SerialPort.setSuPath(suPath);
|
||||
isMockMode = false;
|
||||
} catch (LogsSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
Log.e("Rs232Dev", "Root permission (su) not found, entering Mock Mode: " + e.getMessage());
|
||||
suPath = null;
|
||||
isMockMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,17 +78,21 @@ public class Rs232Dev {
|
||||
private void initDeviceList() {
|
||||
if (rs232DeviceList == null) rs232DeviceList = new ArrayList<>();
|
||||
if (rs232DeviceList.isEmpty()) {
|
||||
for (ComPort comPort: ComPort.values()) {
|
||||
for (ComPort comPort : ComPort.values()) {
|
||||
if (chmod666File(comPort.getFilepath())) {
|
||||
rs232DeviceList.add(comPort.getFilepath());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rs232DeviceList.isEmpty()) {
|
||||
isMockMode = true;
|
||||
Log.w("Rs232Dev", "No serial ports found, entering Mock Mode.");
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBuffer() throws LogsIOException {
|
||||
if (inputStream == null) {
|
||||
logs.info("InputStream 為 null,無法清除緩衝區");
|
||||
if (isMockMode || inputStream == null) {
|
||||
if (showLogs) logs.info(isMockMode ? "[MockMode] Skip clearBuffer" : "InputStream 為 null,無法清除緩衝區");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -135,7 +144,7 @@ public class Rs232Dev {
|
||||
stdoutThread.join();
|
||||
stderrThread.join(); // 確保執行緒執行完畢
|
||||
|
||||
if (su.waitFor() != 0) {
|
||||
if (exitCode != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -166,28 +175,26 @@ public class Rs232Dev {
|
||||
}
|
||||
|
||||
public void connectPort(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
|
||||
// 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
|
||||
// Check bit; 0: no check bit (NONE, default); 1: odd check bit (ODD); 2: even check bit (EVEN)
|
||||
// .parity(2)
|
||||
// 数据位,默认8;可选值为5~8
|
||||
// Data bit, default 8; optional value is 5~8
|
||||
// .dataBits(7)
|
||||
// 停止位,默认1;1:1位停止位;2:2位停止位
|
||||
// Stop bit, default 1; 1:1 stop bit; 2: 2 stop bit
|
||||
// .stopBits(2)
|
||||
if (isMockMode) {
|
||||
Log.i("Rs232Dev", "[MockMode] Faking connection to: " + comPort.getFilepath());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (rs232DeviceList.contains(comPort.getFilepath())) {
|
||||
// closePort();
|
||||
this.serialPort = SerialPort
|
||||
.newBuilder(comPort.getFilepath(), comPort.getBaudRate())
|
||||
.build();
|
||||
if (showLogs) Log.i("connect", "file:" + comPort.getFilepath() + " baudrate:" + comPort.getBaudRate());
|
||||
this.inputStream = this.serialPort.getInputStream();
|
||||
this.outputStream = this.serialPort.getOutputStream();
|
||||
} else {
|
||||
throw new LogsIOException(logs, "Serialport not found:" + comPort.getFilepath());
|
||||
Log.w("Rs232Dev", "Serialport not found: " + comPort.getFilepath() + ", entering Mock Mode.");
|
||||
isMockMode = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new LogsIOException(logs, e.getLocalizedMessage());
|
||||
Log.e("Rs232Dev", "Connect failed: " + e.getMessage() + ", entering Mock Mode.");
|
||||
isMockMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +258,7 @@ public class Rs232Dev {
|
||||
}
|
||||
|
||||
public int read(byte[] buffer) throws LogsIOException {
|
||||
if (isMockMode || this.serialPort == null) return 0;
|
||||
try {
|
||||
InputStream inputStream = this.serialPort.getInputStream();
|
||||
int size = inputStream.read(buffer);
|
||||
@ -262,6 +270,10 @@ public class Rs232Dev {
|
||||
}
|
||||
|
||||
public void send(byte[] buffer) throws LogsIOException {
|
||||
if (isMockMode || this.serialPort == null) {
|
||||
Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(buffer));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OutputStream outputStream = this.serialPort.getOutputStream();
|
||||
outputStream.write(buffer);
|
||||
@ -271,7 +283,12 @@ public class Rs232Dev {
|
||||
throw new LogsIOException(logs, "send:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int transmission(byte[] sendBuffer, byte[] readBuffer) throws LogsIOException {
|
||||
if (isMockMode || this.serialPort == null) {
|
||||
Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(sendBuffer));
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
OutputStream outputStream = this.serialPort.getOutputStream();
|
||||
InputStream inputStream = this.serialPort.getInputStream();
|
||||
@ -309,6 +326,7 @@ public class Rs232Dev {
|
||||
|
||||
// ✅ 新增真正非阻塞讀取方法
|
||||
public byte[] readNonBlocking() throws LogsIOException {
|
||||
if (isMockMode || this.serialPort == null) return new byte[0];
|
||||
try {
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
int available = inputStream.available();
|
||||
|
||||
@ -131,9 +131,9 @@ public class HttpAPI {
|
||||
}
|
||||
|
||||
private Logs logs;
|
||||
private static String domainName = "https://unibuy.com.tw/";
|
||||
private String domainNameByUnibuy = domainName + "Unibuy/api/app/";
|
||||
// private String domainNameByUnibuy = domainName + "chi-hai/api/app/";
|
||||
private static String domainName = "https://demo-cloud.setdomains.work/";
|
||||
private String domainNameByUnibuy = domainName + "api/v1/app/";
|
||||
// private String domainNameByUnibuy = domainName + "Unibuy/api/app/";
|
||||
private String domainNameByAAT = domainName + "AAT/api/app/";
|
||||
// private String domainNameByAAT = domainName + "chi-hai-API/api/app/";
|
||||
public static String domainNameByNewHost = "http://152.42.211.122:8000/";
|
||||
@ -652,7 +652,7 @@ public class HttpAPI {
|
||||
|
||||
|
||||
private void downloadSetting(FormPostMainScheduler formPostRunMain) throws LogsSettingEmptyException, LogsParseException {
|
||||
checkMachineInfo();
|
||||
// checkMachineInfo();
|
||||
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
|
||||
392
app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java
vendored
Normal file
392
app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java
vendored
Normal file
@ -0,0 +1,392 @@
|
||||
package com.unibuy.smartdevice.external;
|
||||
|
||||
import android.util.Log;
|
||||
import com.unibuy.smartdevice.MyApp;
|
||||
import com.unibuy.smartdevice.tools.FormGetMainScheduler;
|
||||
import com.unibuy.smartdevice.tools.FormPostMainScheduler;
|
||||
import com.unibuy.smartdevice.tools.HandlerMain;
|
||||
import com.unibuy.smartdevice.tools.HttpConnect;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.unibuy.smartdevice.database.ProductsDao;
|
||||
import com.unibuy.smartdevice.database.SettingsDao;
|
||||
import com.unibuy.smartdevice.structure.ProductStructure;
|
||||
import com.unibuy.smartdevice.structure.SettingStructure;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 專門對接 Star-Cloud 後台的 API 類別
|
||||
*/
|
||||
public class StarCloudAPI {
|
||||
private static final String TAG = "StarCloudAPI";
|
||||
|
||||
// Star-Cloud API 基礎路徑
|
||||
private static final String BASE_URL = "https://demo-cloud.setdomains.work/api/v1/app/";
|
||||
|
||||
private HandlerMain handlerMain;
|
||||
private HandlerMain srcHandlerMain;
|
||||
|
||||
public StarCloudAPI(HandlerMain handlerMain, HandlerMain srcHandlerMain) {
|
||||
this.handlerMain = handlerMain;
|
||||
this.srcHandlerMain = srcHandlerMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* B000: 管理員登入驗證
|
||||
*/
|
||||
public void login(final String account, final String password) {
|
||||
Log.i(TAG, "login() called for account: " + account);
|
||||
final String url = BASE_URL + "admin/login/B000";
|
||||
Log.i(TAG, "Starting B000 request to: " + url);
|
||||
|
||||
// 不依賴 FormPostMainScheduler (會靜默吞掉 IO exception)
|
||||
// 直接用 Thread + HttpURLConnection 確保錯誤一定看得到
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int responseCode = -1;
|
||||
String receive = "";
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("Su_Account", account);
|
||||
json.put("Su_Password", password);
|
||||
json.put("ip", getLocalIpAddress());
|
||||
|
||||
java.net.URL reqUrl = new java.net.URL(url);
|
||||
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) reqUrl.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
// 加入機台 Token 驗證
|
||||
String machineToken = MyApp.getInstance().getMachine().getApiKey();
|
||||
if (machineToken != null && !machineToken.isEmpty()) {
|
||||
conn.setRequestProperty("Authorization", "Bearer " + machineToken);
|
||||
Log.i(TAG, "B000 Request with Token: Bearer " + machineToken.substring(0, Math.min(machineToken.length(), 10)) + "...");
|
||||
}
|
||||
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(15000);
|
||||
conn.setDoOutput(true);
|
||||
|
||||
byte[] bodyBytes = json.toString().getBytes("UTF-8");
|
||||
conn.getOutputStream().write(bodyBytes);
|
||||
conn.getOutputStream().flush();
|
||||
|
||||
responseCode = conn.getResponseCode();
|
||||
java.io.InputStream is = (responseCode >= 200 && responseCode < 300)
|
||||
? conn.getInputStream() : conn.getErrorStream();
|
||||
java.io.BufferedReader br = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(is, "UTF-8"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) sb.append(line);
|
||||
receive = sb.toString();
|
||||
conn.disconnect();
|
||||
|
||||
Log.d(TAG, "B000 Response (" + responseCode + "): " + receive);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "B000 Network Error", e);
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
android.widget.Toast.makeText(MyApp.getInstance(),
|
||||
"連線失敗: " + e.getMessage(), android.widget.Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final int finalCode = responseCode;
|
||||
final String finalReceive = receive;
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (finalCode >= 200 && finalCode < 300) {
|
||||
try {
|
||||
JSONObject res = new JSONObject(finalReceive);
|
||||
if (res.optBoolean("success", false)) {
|
||||
MyApp.getInstance().setSuAccess(res.optString("Su_Account", account));
|
||||
String token = res.optString("token", "");
|
||||
if (!token.isEmpty()) {
|
||||
MyApp.getInstance().getMachine().setApiKey(token);
|
||||
Log.i(TAG, "Login success, persisting token: " + token);
|
||||
// 登入成功立即持久化,防止重啟失效
|
||||
persistCurrentSettings();
|
||||
}
|
||||
handlerMain.start("StarCloudAPI", 200, "登入成功");
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", 401,
|
||||
"登入失敗: " + res.optString("message", "帳號密碼錯誤"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "B000 Parse Error: " + e.getMessage());
|
||||
handlerMain.start("StarCloudAPI", 500, "登入解析失敗: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", finalCode,
|
||||
"網路錯誤 (" + finalCode + "): " + finalReceive);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* B014: 下載機器設定 (綁定機器)
|
||||
* 不需要 ApiKey 即可呼叫,僅需機器序號。
|
||||
*/
|
||||
public void downloadSetting() {
|
||||
String machineId = MyApp.getInstance().getMachine().getMachineID();
|
||||
if (machineId.isEmpty()) {
|
||||
Log.e(TAG, "Machine ID is empty, cannot bind.");
|
||||
handlerMain.start("StarCloudAPI", 400, "請先輸入機器序號");
|
||||
return;
|
||||
}
|
||||
|
||||
String url = BASE_URL + "machine/setting/B014?machine=" + machineId;
|
||||
|
||||
new FormGetMainScheduler(handlerMain) {
|
||||
@Override
|
||||
protected HandlerMain setHandlerMain() {
|
||||
return srcHandlerMain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> setCls() {
|
||||
return StarCloudAPI.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response(int responseCode, String receive, HandlerMain handlerMain) {
|
||||
Log.d(TAG, "B014 Response (" + responseCode + "): " + receive);
|
||||
if (responseCode == 200) {
|
||||
try {
|
||||
JSONObject res = new JSONObject(receive);
|
||||
if (res.optBoolean("success", false)) {
|
||||
// 配合後端更新:直接解析 data 物件
|
||||
JSONObject config = res.optJSONObject("data");
|
||||
if (config != null) {
|
||||
saveSettings(config);
|
||||
handlerMain.start("StarCloudAPI", 200, "綁定成功");
|
||||
} else {
|
||||
// 偵錯用:顯示收到的原始內容
|
||||
handlerMain.start("StarCloudAPI", 500, "設定內容為空,原始資料:" + receive);
|
||||
}
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", 401, "綁定失敗: " + res.optString("message"));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "B014 Parse Error: " + e.getMessage());
|
||||
handlerMain.start("StarCloudAPI", 500, "設定解析錯誤: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
JSONObject res = new JSONObject(receive);
|
||||
String msg = res.optString("message", "網路錯誤: " + responseCode);
|
||||
handlerMain.start("StarCloudAPI", responseCode, msg);
|
||||
} catch (Exception e) {
|
||||
handlerMain.start("StarCloudAPI", responseCode, "網路錯誤: " + responseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(url, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* B012: 同步商品清單
|
||||
* 需要 Bearer Token 認證。
|
||||
*/
|
||||
public void downloadProductInfo() {
|
||||
String url = BASE_URL + "machine/products/B012";
|
||||
|
||||
new FormGetMainScheduler(handlerMain) {
|
||||
@Override
|
||||
protected HandlerMain setHandlerMain() {
|
||||
return srcHandlerMain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> setCls() {
|
||||
return StarCloudAPI.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response(int responseCode, String receive, HandlerMain handlerMain) {
|
||||
Log.d(TAG, "B012 Response (" + responseCode + "): " + receive);
|
||||
if (responseCode == 200) {
|
||||
try {
|
||||
JSONObject res = new JSONObject(receive);
|
||||
if (res.optBoolean("success")) {
|
||||
JSONArray products = res.getJSONArray("data");
|
||||
saveProducts(products);
|
||||
handlerMain.start("StarCloudAPI", 200, "商品同步成功");
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", 401, "同步失敗: " + res.optString("message"));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
handlerMain.start("StarCloudAPI", 500, "商品解析錯誤");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
JSONObject res = new JSONObject(receive);
|
||||
String msg = res.optString("message", "同步網路錯誤: " + responseCode);
|
||||
handlerMain.start("StarCloudAPI", responseCode, msg);
|
||||
} catch (Exception e) {
|
||||
handlerMain.start("StarCloudAPI", responseCode, "同步網路錯誤: " + responseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(url, null, null, getAuthHeader());
|
||||
}
|
||||
|
||||
private Map<String, String> getAuthHeader() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
String token = MyApp.getInstance().getMachine().getApiKey();
|
||||
if (token != null && !token.isEmpty() && !token.equals("null")) {
|
||||
map.put("Authorization", "Bearer " + token);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 將下載回來的設定儲存到 MyApp 中
|
||||
*/
|
||||
private void saveSettings(JSONObject config) {
|
||||
// 使用 optString 避免欄位缺失導致 JSONException
|
||||
|
||||
// 1. 儲存 ApiToken 與 序號
|
||||
String apiToken = config.optString("api_token", "");
|
||||
if (!apiToken.isEmpty()) {
|
||||
MyApp.getInstance().getMachine().setApiKey(apiToken);
|
||||
}
|
||||
|
||||
String machineID = config.optString("t050v01", "");
|
||||
if (!machineID.isEmpty()) {
|
||||
MyApp.getInstance().getMachine().setMachineID(machineID);
|
||||
}
|
||||
|
||||
// 2. 儲存玉山支付設定
|
||||
MyApp.getInstance().getEsunpay().setStoreID(config.optString("t050v41", ""));
|
||||
MyApp.getInstance().getEsunpay().setTermID(config.optString("t050v42", ""));
|
||||
MyApp.getInstance().getEsunpay().setHash(config.optString("t050v43", ""));
|
||||
|
||||
// 3. 儲存趨勢支付設定 (TrendPay)
|
||||
MyApp.getInstance().getGreenpaySetting().setAppId(config.optString("TP_APP_ID", ""));
|
||||
MyApp.getInstance().getGreenpaySetting().setAppKey(config.optString("TP_APP_KEY", ""));
|
||||
MyApp.getInstance().getGreenpaySetting().setPartnerKey(config.optString("TP_PARTNER_KEY", ""));
|
||||
|
||||
// 4. 儲存電子發票設定
|
||||
MyApp.getInstance().getGreenInvoice().setGreenMerchantID(config.optString("t050v34", ""));
|
||||
MyApp.getInstance().getGreenInvoice().setGreenHashKey(config.optString("t050v35", ""));
|
||||
MyApp.getInstance().getGreenInvoice().setGreenHashIV(config.optString("t050v36", ""));
|
||||
MyApp.getInstance().getGreenInvoice().setGreenEmail(config.optString("t050v38", ""));
|
||||
|
||||
// 5. 重要:將所有設定同步回資料庫,防止 App 重啟後消失
|
||||
persistCurrentSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 將目前記憶體中的所有設定(包含 ApiKey)強制持久化到資料庫
|
||||
*/
|
||||
private void persistCurrentSettings() {
|
||||
Log.i(TAG, "persistCurrentSettings() - Saving to DB: machineID=" + MyApp.getInstance().getMachine().getMachineID()
|
||||
+ ", token=" + MyApp.getInstance().getMachine().getApiKey());
|
||||
SettingsDao settingsDao = new SettingsDao(handlerMain.getContext());
|
||||
settingsDao.deleteAll();
|
||||
|
||||
settingsDao.insertOne(new SettingStructure(MyApp.getInstance().getMachine().getClass().getSimpleName(),
|
||||
new String[] {
|
||||
MyApp.getInstance().getMachine().getApiKey(),
|
||||
MyApp.getInstance().getMachine().getMachineID()
|
||||
}));
|
||||
|
||||
settingsDao.insertOne(new SettingStructure(MyApp.getInstance().getEsunpay().getClass().getSimpleName(),
|
||||
new String[] {
|
||||
MyApp.getInstance().getEsunpay().getStoreID(),
|
||||
MyApp.getInstance().getEsunpay().getTermID(),
|
||||
MyApp.getInstance().getEsunpay().getHash()
|
||||
}));
|
||||
|
||||
settingsDao.insertOne(new SettingStructure(MyApp.getInstance().getGreenInvoice().getClass().getSimpleName(),
|
||||
new String[] {
|
||||
MyApp.getInstance().getGreenInvoice().getGreenMerchantID(),
|
||||
MyApp.getInstance().getGreenInvoice().getGreenHashKey(),
|
||||
MyApp.getInstance().getGreenInvoice().getGreenHashIV(),
|
||||
MyApp.getInstance().getGreenInvoice().getGreenEmail()
|
||||
}));
|
||||
|
||||
settingsDao.insertOne(new SettingStructure(MyApp.getInstance().getGreenpaySetting().getClass().getSimpleName(),
|
||||
new String[] {
|
||||
String.valueOf(MyApp.getInstance().getGreenpaySetting().getAppId()),
|
||||
MyApp.getInstance().getGreenpaySetting().getAppKey(),
|
||||
MyApp.getInstance().getGreenpaySetting().getPartnerKey()
|
||||
}));
|
||||
|
||||
settingsDao.close();
|
||||
Log.i(TAG, "Persistence completed successfully.");
|
||||
}
|
||||
|
||||
private void saveProducts(JSONArray products) throws JSONException {
|
||||
MyApp.getInstance().getProductList().clear();
|
||||
for (int i = 0; i < products.length(); i++) {
|
||||
JSONObject p = products.getJSONObject(i);
|
||||
|
||||
// 對應 Star-Cloud 欄位與 App 內部 ProductStructure 欄位
|
||||
ProductStructure product = new ProductStructure(
|
||||
p.optString("t060v00", ""), // PID
|
||||
p.optString("t060v06", ""), // Image
|
||||
p.optString("t060v01", ""), // Name
|
||||
p.optInt("t063v03", 0), // Machine Price
|
||||
p.optInt("t060v09", 0), // Selling Price
|
||||
p.optInt("t060v30", 0), // Member Price
|
||||
p.optInt("t060v11", 99), // Upper Limit
|
||||
p.optString("t060v40", ""), // Plan
|
||||
p.optString("t060v41", ""), // Material
|
||||
p.optString("t060v01_en", ""),
|
||||
p.optString("t060v01_jp", ""));
|
||||
MyApp.getInstance().getProductList().add(product);
|
||||
}
|
||||
|
||||
// 將商品同步至資料庫
|
||||
ProductsDao productsDao = new ProductsDao(handlerMain.getContext());
|
||||
productsDao.deleteAll(); // 先清空舊資料
|
||||
productsDao.insertAll(MyApp.getInstance().getProductList()); // 批量插入
|
||||
productsDao.close();
|
||||
|
||||
Log.i(TAG, "Synced and persisted " + products.length() + " products from star-cloud.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得設備真實 IP 地址
|
||||
*/
|
||||
private String getLocalIpAddress() {
|
||||
try {
|
||||
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface intf : interfaces) {
|
||||
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
|
||||
for (InetAddress addr : addrs) {
|
||||
if (!addr.isLoopbackAddress()) {
|
||||
String sAddr = addr.getHostAddress();
|
||||
boolean isIPv4 = sAddr.indexOf(':') < 0;
|
||||
if (isIPv4)
|
||||
return sAddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Get IP Error", ex);
|
||||
}
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,9 +16,9 @@ public class MachineStructure {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// public void setApiKey(String apiKey) {
|
||||
// this.apiKey = apiKey;
|
||||
// }
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public String getMachineID() {
|
||||
return machineID;
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package com.unibuy.smartdevice.tools;
|
||||
|
||||
import com.unibuy.smartdevice.exception.LogsIOException;
|
||||
import com.unibuy.smartdevice.exception.LogsMalformedURLException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class FormGetMainScheduler extends HandlerMainScheduler {
|
||||
private String urlString;
|
||||
private String cookie;
|
||||
private String referer;
|
||||
private Map<String, String> mapByRequestProperty;
|
||||
|
||||
public FormGetMainScheduler(HandlerMain srcHandlerMain) {
|
||||
super(srcHandlerMain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(HandlerMain handlerMain) {
|
||||
try {
|
||||
HttpConnect httpConnect = new HttpConnect();
|
||||
int responseCode = httpConnect.formGet(getUrlString(), getCookie(), getReferer(), mapByRequestProperty);
|
||||
getLogs().info("(" + responseCode + ")receive:" + httpConnect.getRecevieData());
|
||||
response(responseCode, httpConnect.getRecevieData(), handlerMain);
|
||||
} catch (LogsMalformedURLException | LogsIOException e) {
|
||||
getLogs().warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void start(String urlString, String cookie, String referer, Map<String, String> mapByRequestProperty) {
|
||||
this.urlString = urlString;
|
||||
this.cookie = cookie;
|
||||
this.referer = referer;
|
||||
this.mapByRequestProperty = mapByRequestProperty;
|
||||
super.start();
|
||||
}
|
||||
|
||||
public abstract void response(int responseCode, String receive, HandlerMain handlerMain);
|
||||
|
||||
String getUrlString() {
|
||||
return urlString;
|
||||
}
|
||||
|
||||
String getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
String getReferer() {
|
||||
return referer;
|
||||
}
|
||||
}
|
||||
@ -35,11 +35,16 @@ public abstract class FormPostMainScheduler extends HandlerMainScheduler {
|
||||
}
|
||||
|
||||
public void start(String urlString, String postData, String cookie, String referer, HttpConnect.ContentType contextType) {
|
||||
start(urlString, postData, cookie, referer, contextType, null);
|
||||
}
|
||||
|
||||
public void start(String urlString, String postData, String cookie, String referer, HttpConnect.ContentType contextType, Map<String, String> mapByRequestProperty) {
|
||||
this.urlString = urlString;
|
||||
this.data = postData;
|
||||
this.cookie = cookie;
|
||||
this.referer = referer;
|
||||
this.contextType = contextType;
|
||||
this.mapByRequestProperty = mapByRequestProperty;
|
||||
super.start();
|
||||
}
|
||||
|
||||
|
||||
@ -61,6 +61,62 @@ public class HttpConnect {
|
||||
this.logs = new Logs(this.getClass());
|
||||
}
|
||||
|
||||
public int formGet(String urlString, String cookie, String referer, Map<String, String> mapByRequestProperty) throws LogsMalformedURLException, LogsIOException {
|
||||
logs.debug("[form get]" + "url:" + urlString);
|
||||
|
||||
String charset = "UTF-8";
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
|
||||
httpURLConnection.setDoInput(true);
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
httpURLConnection.setUseCaches(false);
|
||||
httpURLConnection.setInstanceFollowRedirects(true);
|
||||
HttpURLConnection.setFollowRedirects(true);
|
||||
|
||||
if (cookie != null) {
|
||||
httpURLConnection.setRequestProperty("Cookie", cookie);
|
||||
}
|
||||
if (referer != null) {
|
||||
httpURLConnection.setRequestProperty("Referer", referer);
|
||||
}
|
||||
if (mapByRequestProperty != null) {
|
||||
for (String key: mapByRequestProperty.keySet()) {
|
||||
httpURLConnection.setRequestProperty(key, mapByRequestProperty.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
this.responseCode = httpURLConnection.getResponseCode();
|
||||
|
||||
InputStream inputStream;
|
||||
if (this.responseCode >= 200 && this.responseCode < 300) {
|
||||
inputStream = httpURLConnection.getInputStream();
|
||||
} else {
|
||||
inputStream = httpURLConnection.getErrorStream();
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charset));
|
||||
StringBuilder receive = new StringBuilder();
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
receive.append(line);
|
||||
}
|
||||
this.recevieData = receive.toString();
|
||||
bufferedReader.close();
|
||||
} else {
|
||||
this.recevieData = "";
|
||||
}
|
||||
|
||||
httpURLConnection.disconnect();
|
||||
return this.responseCode;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new LogsMalformedURLException(logs, ErrorCode.INVALID_URL_FORMAT, e);
|
||||
} catch (IOException e) {
|
||||
throw new LogsIOException(logs, ErrorCode.NETWORK_CONNECTION_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
public int formPost(String urlString, String data, String cookie, String referer, ContentType contentType, Map<String, String> mapByRequestProperty) throws LogsMalformedURLException, LogsIOException {
|
||||
logs.debug("[form post]" + "url:" + urlString + " data:" + data);
|
||||
|
||||
|
||||
@ -255,9 +255,9 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
getLogs().clearMessage();
|
||||
|
||||
try {
|
||||
httpAPI.detectHost();
|
||||
} catch (LogsParseException | LogsSettingEmptyException e) {
|
||||
getLogs().warning(e);
|
||||
// httpAPI.detectHost();
|
||||
} catch (Exception e) {
|
||||
// getLogs().warning(e);
|
||||
}
|
||||
|
||||
String timeByS = new SimpleDateFormat("ss").format(new Date());
|
||||
@ -289,10 +289,10 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
// 確保距離上次執行至少間隔 9 分鐘(540000 毫秒)
|
||||
if (currentTime - lastReportTime >= 540000) {
|
||||
try {
|
||||
httpAPI.deviceReport();
|
||||
// httpAPI.deviceReport();
|
||||
lastReportTime = currentTime;
|
||||
} catch (LogsSettingEmptyException | LogsParseException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -599,66 +599,11 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
// binding.btnswitchlanJa.setOnClickListener(v -> switchLanguage("ja"));
|
||||
|
||||
|
||||
// // 假設你使用 ViewBinding
|
||||
// binding.btnTopRight.setOnClickListener(v -> {
|
||||
// long currentTime = System.currentTimeMillis();
|
||||
//
|
||||
// // 如果是第一次點擊,開始計時
|
||||
//
|
||||
// if (clickCount == 0) {
|
||||
// firstClickTime = currentTime;
|
||||
// isTiming = true; // 開始計時
|
||||
// }
|
||||
// // 增加點擊次數
|
||||
// clickCount++;
|
||||
// // 檢查是否超過 20 秒
|
||||
// if (isTiming && currentTime - firstClickTime > TIME_LIMIT) {
|
||||
// // 超過 20 秒後,停止計時並重置
|
||||
// isTiming = false;
|
||||
// clickCount = 0;
|
||||
// firstClickTime = 0;
|
||||
// }
|
||||
//
|
||||
// // 檢查是否已經連點五下
|
||||
// if (clickCount >= 8 && isTiming) {
|
||||
// // 點擊滿五次且在20秒內,啟動新活動
|
||||
//
|
||||
// getTools().gotoActivity(MainActivity.class);
|
||||
// finish();
|
||||
//
|
||||
//// AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
//// builder.setTitle("請輸入密碼");
|
||||
////
|
||||
//// // 建立 EditText 讓使用者輸入
|
||||
//// final EditText input = new EditText(this);
|
||||
//// input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
//// input.setHint("請輸入 4 位數密碼");
|
||||
//// builder.setView(input);
|
||||
////
|
||||
//// builder.setPositiveButton("確定", (dialog, which) -> {
|
||||
//// String password = input.getText().toString();
|
||||
//// if ("0000".equals(password)) {
|
||||
//// // 密碼正確,跳轉
|
||||
//// Intent intent = new Intent(this, FlowerSlotListActivity.class);
|
||||
//// startActivity(intent);
|
||||
//// } else {
|
||||
//// // 密碼錯誤,提示
|
||||
//// Toast.makeText(this, "密碼錯誤", Toast.LENGTH_SHORT).show();
|
||||
//// }
|
||||
//// });
|
||||
////
|
||||
//// builder.setNegativeButton("取消", (dialog, which) -> dialog.cancel());
|
||||
////
|
||||
//// builder.show();
|
||||
//
|
||||
//
|
||||
// // 重置點擊次數和計時
|
||||
// clickCount = 0;
|
||||
// firstClickTime = 0;
|
||||
// isTiming = false;
|
||||
// }
|
||||
//
|
||||
// });
|
||||
// 假設你使用 ViewBinding
|
||||
binding.btnTopRight.setOnClickListener(v -> {
|
||||
getTools().gotoActivity(MainActivity.class);
|
||||
finish();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import com.unibuy.smartdevice.databinding.ActivitySettingBinding;
|
||||
import com.unibuy.smartdevice.exception.LogsParseException;
|
||||
import com.unibuy.smartdevice.exception.LogsSettingEmptyException;
|
||||
import com.unibuy.smartdevice.external.HttpAPI;
|
||||
import com.unibuy.smartdevice.external.StarCloudAPI;
|
||||
import com.unibuy.smartdevice.tools.HandlerMain;
|
||||
import com.unibuy.smartdevice.tools.HandlerMainSchedulerOnClick;
|
||||
import com.unibuy.smartdevice.tools.ToastHandlerMain;
|
||||
@ -34,6 +35,8 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
TOAST(0),
|
||||
CHECK_AND_SAVE_STATUS(1),
|
||||
DATA_RELOAD(2),
|
||||
SUCCESS(200),
|
||||
FAILED(400),
|
||||
;
|
||||
|
||||
private int option;
|
||||
@ -71,26 +74,55 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
protected void execute(Context context, int commandCode, String message) {
|
||||
Option option = optionMap.get(commandCode);
|
||||
|
||||
if (option == null) {
|
||||
// 如果拿到未定義的代碼 (例如 404, 500),視同失敗,關掉進度條並顯示訊息
|
||||
super.execute(context, commandCode, message);
|
||||
binding.progressLayout.setVisibility(View.GONE);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case TOAST:
|
||||
super.execute(context, commandCode, message);
|
||||
super.execute(context, commandCode, message);
|
||||
break;
|
||||
case CHECK_AND_SAVE_STATUS:
|
||||
super.execute(context, commandCode, message);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
checkSetting();
|
||||
saveStatusList(context);
|
||||
super.execute(context, commandCode, message);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
checkSetting();
|
||||
saveStatusList(context);
|
||||
break;
|
||||
case DATA_RELOAD:
|
||||
binding.buttonBack.setVisibility(View.INVISIBLE);
|
||||
MyApp.getInstance().getMachine().setMachineID(binding.editMachineID.getText().toString());
|
||||
|
||||
binding.progressLayout.setVisibility(View.VISIBLE); // 確保進度條顯示
|
||||
try {
|
||||
httpAPI.downloadSetting();
|
||||
} catch (LogsParseException | LogsSettingEmptyException e) {
|
||||
starCloudAPI.downloadSetting();
|
||||
} catch (Exception e) {
|
||||
getLogs().warning(e);
|
||||
binding.progressLayout.setVisibility(View.GONE);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
}
|
||||
break;
|
||||
case SUCCESS:
|
||||
super.execute(context, commandCode, message);
|
||||
binding.progressLayout.setVisibility(View.GONE);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
|
||||
// 綁定成功後,自動連動執行 B012 同步商品
|
||||
if ("綁定成功".equals(message)) {
|
||||
binding.progressLayout.setVisibility(View.VISIBLE); // 顯示進度條,因為要同步商品
|
||||
starCloudAPI.downloadProductInfo();
|
||||
}
|
||||
|
||||
checkSetting();
|
||||
saveStatusList(context);
|
||||
break;
|
||||
case FAILED:
|
||||
super.execute(context, commandCode, message);
|
||||
binding.progressLayout.setVisibility(View.GONE);
|
||||
binding.buttonBack.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -100,6 +132,7 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
private List<String> statusList = new ArrayList<>();
|
||||
private RecyclerSettingListAdpter recyclerSettingListAdpter;
|
||||
private HttpAPI httpAPI;
|
||||
private StarCloudAPI starCloudAPI;
|
||||
private static ProgressBar progressBar;
|
||||
private static TextView progressText;
|
||||
private static WeakReference<SettingActivity> instance;
|
||||
@ -113,6 +146,7 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
httpAPI = new HttpAPI(getHandlerMain());
|
||||
starCloudAPI = new StarCloudAPI(getHandlerMain(), getHandlerMain());
|
||||
instance = new WeakReference<>(this);
|
||||
progressBar = binding.progressBar;
|
||||
progressText = binding.progressText;
|
||||
|
||||
@ -254,16 +254,16 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTopRight"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="400dp"
|
||||
android:text="右上角按鈕"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="100dp"
|
||||
android:text="進入後台"
|
||||
android:textSize="24sp"
|
||||
android:backgroundTint="@color/gray"
|
||||
android:backgroundTint="@color/red"
|
||||
android:textColor="@android:color/white"
|
||||
android:alpha="0"
|
||||
android:alpha="1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="visible"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -1,3 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVendor=jetbrains
|
||||
toolchainVersion=21
|
||||
#toolchainVendor=jetbrains
|
||||
#toolchainVersion=21
|
||||
|
||||
Loading…
Reference in New Issue
Block a user