diff --git a/app/build.gradle b/app/build.gradle index e2c31e7..d1d6963 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // ========================================================================= def verMajor = 10 def verMinor = 1 -def verPatch = 1 +def verPatch = 6 android { namespace 'com.unibuy.smartdevice' diff --git a/app/src/main/java/com/unibuy/smartdevice/MainActivity.java b/app/src/main/java/com/unibuy/smartdevice/MainActivity.java index b7d1491..497f3e0 100644 --- a/app/src/main/java/com/unibuy/smartdevice/MainActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/MainActivity.java @@ -17,8 +17,11 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; +import android.view.LayoutInflater; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import androidx.core.graphics.Insets; @@ -69,6 +72,8 @@ public class MainActivity extends AppCompatActivityAbstract { return this.getClass(); } + private AlertDialog activeLoginDialog; + @Override protected HandlerMain setHandlerMain() { return new ToastHandlerMain(getCtx(), getLogs()) { @@ -76,6 +81,11 @@ public class MainActivity extends AppCompatActivityAbstract { public void execute(Context context, int commandCode, String message) { super.execute(context, commandCode, message); if ("登入成功".equals(message)) { + updateLoginStatus(); + if (activeLoginDialog != null && activeLoginDialog.isShowing()) { + activeLoginDialog.dismiss(); + activeLoginDialog = null; + } if (pendingAction != null) { pendingAction.run(); pendingAction = null; @@ -111,79 +121,66 @@ public class MainActivity extends AppCompatActivityAbstract { // ---- 環境尺寸設定 ---- initialData(); + updateLoginStatus(); + + binding.btnStatusAction.setOnClickListener(v -> { + if (MyApp.getInstance().getSuAccess().isEmpty()) { + showLoginDialog(getCtx(), this::updateLoginStatus); + } else { + MyApp.getInstance().setSuAccess(""); + updateLoginStatus(); + Toast.makeText(getCtx(), "已登出後台權限", Toast.LENGTH_SHORT).show(); + } + }); binding.buttonGotoSetting.setOnClickListener(v -> { - pendingAction = new Runnable() { - @Override - public void run() { - getTools().gotoActivity(SettingActivity.class); - } - }; + Runnable action = () -> getTools().gotoActivity(SettingActivity.class); if (!MyApp.getInstance().getMachine().getMachineID().isEmpty()) { if (MyApp.getInstance().getSuAccess().isEmpty()) { - showLoginDialog(getCtx()); + showLoginDialog(getCtx(), action); return; } } - pendingAction.run(); - pendingAction = null; + action.run(); }); binding.buttonGotoSystemSet.setOnClickListener(v -> { - pendingAction = new Runnable() { - @Override - public void run() { - getTools().gotoActivity(SystemSetActivity.class); - } - }; + Runnable action = () -> getTools().gotoActivity(SystemSetActivity.class); if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) { getHandlerMain().start("MainActivity", "@先綁定機器"); return; } if (MyApp.getInstance().getSuAccess().isEmpty()) { - showLoginDialog(getCtx()); + showLoginDialog(getCtx(), action); return; } - pendingAction.run(); - pendingAction = null; + action.run(); }); binding.buttonGotoProductList.setOnClickListener(v -> { - pendingAction = new Runnable() { - @Override - public void run() { - getTools().gotoActivity(ProductListActivity.class); - } - }; + Runnable action = () -> getTools().gotoActivity(ProductListActivity.class); if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) { getHandlerMain().start("MainActivity", "@先綁定機器"); return; } if (MyApp.getInstance().getSuAccess().isEmpty()) { - showLoginDialog(getCtx()); + showLoginDialog(getCtx(), action); return; } - pendingAction.run(); - pendingAction = null; + action.run(); }); binding.buttonGotoSlotList.setOnClickListener(v -> { - pendingAction = new Runnable() { - @Override - public void run() { - getTools().gotoActivity(VmcSlotListActivity.class); - } - }; + Runnable action = () -> getTools().gotoActivity(VmcSlotListActivity.class); if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) { getHandlerMain().start("MainActivity", "@先綁定機器"); return; } if (MyApp.getInstance().getSuAccess().isEmpty()) { - showLoginDialog(getCtx()); + showLoginDialog(getCtx(), action); return; } - pendingAction.run(); - pendingAction = null; + action.run(); }); binding.buttonGotoShoppingPlatform.setOnClickListener(v -> { @@ -247,17 +244,18 @@ public class MainActivity extends AppCompatActivityAbstract { }); binding.buttonGotoXyshjTest.setOnClickListener(v -> { + Runnable action = () -> getTools().gotoActivity(XyshjTestActivity.class); if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) { getHandlerMain().start("MainActivity", "@先綁定機器"); return; } if (MyApp.getInstance().getSuAccess().isEmpty()) { - showLoginDialog(getCtx()); + showLoginDialog(getCtx(), action); return; } - getTools().gotoActivity(XyshjTestActivity.class); + action.run(); }); binding.buttonresetapp.setOnClickListener(v -> { @@ -265,12 +263,15 @@ public class MainActivity extends AppCompatActivityAbstract { ReSetAPP(); }); - binding.buttonOpenSystemSettings.setOnClickListener(v -> { - // 開啟 Android 系統設定 - Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + binding.buttonGotoAndroidSettings.setOnClickListener(v -> { + try { + Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS); + startActivity(intent); + } catch (Exception e) { + Toast.makeText(getCtx(), "無法開啟安卓設定頁", Toast.LENGTH_SHORT).show(); + } }); + } private static class ButtonQrCodeTestOnClick extends HandlerMainSchedulerOnClick { @@ -476,10 +477,16 @@ public class MainActivity extends AppCompatActivityAbstract { } private void initialData() { - if (MyApp.getInstance().getMachine() != null || MyApp.getInstance().getMachine().getMachineID().isEmpty()) { + if (MyApp.getInstance().getMachine() != null && MyApp.getInstance().getMachine().getMachineID().isEmpty()) { SettingsDao settingsDao = new SettingsDao(this); SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getMachine().getClass().getSimpleName()); - MyApp.getInstance().getMachine().setMachineID(setting.getData(1)); + if (setting != null && setting.getDataCount() >= 2) { + MyApp.getInstance().getMachine().setApiKey(setting.getData(0)); + MyApp.getInstance().getMachine().setMachineID(setting.getData(1)); + if (setting.getDataCount() >= 3 && setting.getData(2) != null && !setting.getData(2).isEmpty()) { + MyApp.getInstance().getMachine().setMqttEnv(setting.getData(2)); + } + } settingsDao.close(); } } @@ -518,88 +525,94 @@ public class MainActivity extends AppCompatActivityAbstract { } public void showLoginDialog(Context context) { - // 創建一個垂直的 LinearLayout - LinearLayout layout = new LinearLayout(context); - layout.setOrientation(LinearLayout.VERTICAL); - layout.setPadding(50, 20, 50, 10); + showLoginDialog(context, null); + } - // 帳號輸入框 - final EditText accessInput = new EditText(context); - accessInput.setHint("帳號"); - layout.addView(accessInput); + public void showLoginDialog(Context context, Runnable onSuccess) { + // 使用 LayoutInflater 載入自訂佈局 + View view = LayoutInflater.from(context).inflate(R.layout.dialog_admin_login, null); + + final EditText accessInput = view.findViewById(R.id.etAccess); + final EditText passwordInput = view.findViewById(R.id.etPassword); + final CheckBox rememberAccountCb = view.findViewById(R.id.cbRemember); + androidx.appcompat.widget.AppCompatButton positiveButton = view.findViewById(R.id.btnLogin); + androidx.appcompat.widget.AppCompatButton negativeButton = view.findViewById(R.id.btnCancel); getTools().setImmHideByFocusChange(accessInput); getTools().setImmOpenByFocusChange(accessInput); - - // 密碼輸入框 - final EditText passwordInput = new EditText(context); - passwordInput.setHint("密碼"); - passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - layout.addView(passwordInput); - getTools().setImmHideByFocusChange(passwordInput); + // 讀取紀錄的帳號 + android.content.SharedPreferences prefs = context.getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE); + String savedAccount = prefs.getString("saved_account", ""); + if (!savedAccount.isEmpty()) { + accessInput.setText(savedAccount); + } + rememberAccountCb.setChecked(!savedAccount.isEmpty()); // 如果有存過帳號,預設打勾 + + // 建立對話框 AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle("登入") - .setView(layout) - .setPositiveButton("登入", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String access = accessInput.getText().toString().trim(); - String password = passwordInput.getText().toString().trim(); + .setView(view) + .create(); + activeLoginDialog = dialog; - if (access.isEmpty() || password.isEmpty()) { - Toast.makeText(context, "請輸入帳號和密碼", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(context, "正在驗證登入...", Toast.LENGTH_SHORT).show(); - starCloudAPI.login(access, password); - } + // 設定對話框視窗背景為透明,以便呈現圓角設計 + if (dialog.getWindow() != null) { + dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); + } - getTools().hideSystemBars((Activity) getCtx()); - } - }) - .setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - getTools().hideSystemBars((Activity) getCtx()); - dialog.dismiss(); - } - }).create(); + // 設定點擊事件 + positiveButton.setOnClickListener(v -> { + String access = accessInput.getText().toString().trim(); + String password = passwordInput.getText().toString().trim(); - // 🛠️ Debug Backdoor: 長按對話框標題 3 秒跳過登入 - dialog.setOnShowListener(d -> { - int titleId = context.getResources().getIdentifier("alertTitle", "id", "android"); - View titleView = dialog.findViewById(titleId); - if (titleView != null) { - final Handler titleHandler = new Handler(); - final Runnable titleRunnable = () -> { - Toast.makeText(context, "🛠️ Debug Mode: 跳過登入", Toast.LENGTH_SHORT).show(); - MyApp.getInstance().setSuAccess("DEBUG_MODE"); - if (pendingAction != null) { - pendingAction.run(); - pendingAction = null; - } - dialog.dismiss(); - }; - - titleView.setOnTouchListener((v, event) -> { - switch (event.getAction()) { - case android.view.MotionEvent.ACTION_DOWN: - titleHandler.postDelayed(titleRunnable, 2000); // 2秒 - break; - case android.view.MotionEvent.ACTION_UP: - case android.view.MotionEvent.ACTION_CANCEL: - titleHandler.removeCallbacks(titleRunnable); - break; - } - return true; - }); + if (access.isEmpty() || password.isEmpty()) { + Toast.makeText(context, "請輸入帳號和密碼", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(context, "正在驗證登入...", Toast.LENGTH_SHORT).show(); + + // 為了處理 StarCloudAPI 登入成功後的回調 + // 我們重新設計 handlerMain 讓它能夠在 onSuccess 回調中跑 pendingAction + pendingAction = onSuccess; + starCloudAPI.login(access, password); + + if (rememberAccountCb.isChecked()) { + prefs.edit().putString("saved_account", access).apply(); + } else { + prefs.edit().remove("saved_account").apply(); + } + // 🛠️ 關鍵優化:不再此處立即 dismiss(),改由 API 回報成功時關閉 } + getTools().hideSystemBars((Activity) getCtx()); + }); + + negativeButton.setOnClickListener(v -> { + getTools().hideSystemBars((Activity) getCtx()); + dialog.dismiss(); + activeLoginDialog = null; }); dialog.show(); } + /** + * 更新右上角登入狀態顯示 + */ + private void updateLoginStatus() { + String suAccess = MyApp.getInstance().getSuAccess(); + if (suAccess.isEmpty()) { + binding.tvWelcomeTitle.setText("歡迎使用 AAT\n智能販賣機系統"); + binding.tvWelcomeHint.setText("請點擊右上角登入帳號"); + binding.btnStatusAction.setText("登入"); + binding.btnStatusAction.setBackgroundTintList(android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.primary))); + } else { + binding.tvWelcomeTitle.setText("歡迎使用 AAT\n" + suAccess); + binding.tvWelcomeHint.setText("請點擊下方按鈕操作"); + binding.btnStatusAction.setText("登出"); + binding.btnStatusAction.setBackgroundTintList(android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.error))); + } + } + private void enterShoppingPlatform() { MyApp.getInstance().setSuAccess(""); getTools().gotoActivity(FontendActivity.class); @@ -634,15 +647,15 @@ public class MainActivity extends AppCompatActivityAbstract { super.onStart(); if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) { - binding.buttonGotoSystemSet.setVisibility(View.GONE); - binding.buttonGotoProductList.setVisibility(View.GONE); - binding.buttonGotoSlotList.setVisibility(View.GONE); - binding.buttonGotoShoppingPlatform.setVisibility(View.GONE); + binding.wrapButtonGotoSystemSet.setVisibility(View.GONE); + binding.wrapButtonGotoProductList.setVisibility(View.GONE); + binding.wrapButtonGotoSlotList.setVisibility(View.GONE); + binding.wrapButtonGotoShoppingPlatform.setVisibility(View.GONE); } else { - binding.buttonGotoSystemSet.setVisibility(View.VISIBLE); - binding.buttonGotoProductList.setVisibility(View.VISIBLE); - binding.buttonGotoSlotList.setVisibility(View.VISIBLE); - binding.buttonGotoShoppingPlatform.setVisibility(View.VISIBLE); + binding.wrapButtonGotoSystemSet.setVisibility(View.VISIBLE); + binding.wrapButtonGotoProductList.setVisibility(View.VISIBLE); + binding.wrapButtonGotoSlotList.setVisibility(View.VISIBLE); + binding.wrapButtonGotoShoppingPlatform.setVisibility(View.VISIBLE); } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_button_outline.xml b/app/src/main/res/drawable/bg_button_outline.xml new file mode 100644 index 0000000..a48aff9 --- /dev/null +++ b/app/src/main/res/drawable/bg_button_outline.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_button_primary.xml b/app/src/main/res/drawable/bg_button_primary.xml new file mode 100644 index 0000000..37dcebb --- /dev/null +++ b/app/src/main/res/drawable/bg_button_primary.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_input_modern.xml b/app/src/main/res/drawable/bg_input_modern.xml new file mode 100644 index 0000000..46ec401 --- /dev/null +++ b/app/src/main/res/drawable/bg_input_modern.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/src/main/res/drawable/binding_machine.png b/app/src/main/res/drawable/binding_machine.png new file mode 100644 index 0000000..b216654 Binary files /dev/null and b/app/src/main/res/drawable/binding_machine.png differ diff --git a/app/src/main/res/drawable/card_reader_checkout.png b/app/src/main/res/drawable/card_reader_checkout.png new file mode 100644 index 0000000..5baa1ea Binary files /dev/null and b/app/src/main/res/drawable/card_reader_checkout.png differ diff --git a/app/src/main/res/drawable/card_reader_reboot.png b/app/src/main/res/drawable/card_reader_reboot.png new file mode 100644 index 0000000..c8a525e Binary files /dev/null and b/app/src/main/res/drawable/card_reader_reboot.png differ diff --git a/app/src/main/res/drawable/cargo_lane_management.png b/app/src/main/res/drawable/cargo_lane_management.png new file mode 100644 index 0000000..4507ac9 Binary files /dev/null and b/app/src/main/res/drawable/cargo_lane_management.png differ diff --git a/app/src/main/res/drawable/product_list.png b/app/src/main/res/drawable/product_list.png new file mode 100644 index 0000000..00281d2 Binary files /dev/null and b/app/src/main/res/drawable/product_list.png differ diff --git a/app/src/main/res/drawable/reboot_app.png b/app/src/main/res/drawable/reboot_app.png new file mode 100644 index 0000000..e9d08f4 Binary files /dev/null and b/app/src/main/res/drawable/reboot_app.png differ diff --git a/app/src/main/res/drawable/sales_page.png b/app/src/main/res/drawable/sales_page.png new file mode 100644 index 0000000..64022fe Binary files /dev/null and b/app/src/main/res/drawable/sales_page.png differ diff --git a/app/src/main/res/drawable/scanner_test.png b/app/src/main/res/drawable/scanner_test.png new file mode 100644 index 0000000..d69e0d5 Binary files /dev/null and b/app/src/main/res/drawable/scanner_test.png differ diff --git a/app/src/main/res/drawable/system_settings.png b/app/src/main/res/drawable/system_settings.png new file mode 100644 index 0000000..8c2a76d Binary files /dev/null and b/app/src/main/res/drawable/system_settings.png differ diff --git a/app/src/main/res/drawable/taiwan_star_tech_logo.png b/app/src/main/res/drawable/taiwan_star_tech_logo.png new file mode 100644 index 0000000..cec04eb Binary files /dev/null and b/app/src/main/res/drawable/taiwan_star_tech_logo.png differ diff --git a/app/src/main/res/drawable/vending_machine_scenario_v1_remove_background.png b/app/src/main/res/drawable/vending_machine_scenario_v1_remove_background.png new file mode 100644 index 0000000..a494bee Binary files /dev/null and b/app/src/main/res/drawable/vending_machine_scenario_v1_remove_background.png differ diff --git a/app/src/main/res/drawable/vending_machine_scenario_v2_remove_background.png b/app/src/main/res/drawable/vending_machine_scenario_v2_remove_background.png new file mode 100644 index 0000000..d88a747 Binary files /dev/null and b/app/src/main/res/drawable/vending_machine_scenario_v2_remove_background.png differ diff --git a/app/src/main/res/drawable/view_log_records.png b/app/src/main/res/drawable/view_log_records.png new file mode 100644 index 0000000..a7dacb1 Binary files /dev/null and b/app/src/main/res/drawable/view_log_records.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8547462..d8860d3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,276 +6,822 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" - android:background="@color/light_gray"> + android:background="@color/white"> - + + + + + + + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/headerBarrier"> + android:padding="24dp"> -