[FEAT] 主控台 UI 全面翻新 (Console UI Overhaul)

1. MainActivity.java:重構按鈕點擊邏輯,改用 Lambda 取代匿名 Runnable;新增 activeLoginDialog 追蹤機制,登入成功後自動關閉對話框並呼叫 updateLoginStatus();新增頂部登入/登出狀態按鈕 (btnStatusAction) 切換邏輯。
2. activity_main.xml:全面翻新 UI 佈局,導入卡片式分組選單、現代化 Header 區塊(含 Logo、版本號、機台狀態)、功能按鈕網格排版,以及底部版權列。
3. colors.xml:建立完整 Material 3 色彩系統,包含 primary/secondary/surface/background 色階,以及 success/warning/error/info 四種語意狀態色;調整 black 為深灰 (#272727)。
4. dimens.xml:新增 welcome 標題、Header Logo、選單按鈕圖示及間距等全套尺寸規格變數。
5. 新增三個 Drawable 資源:bg_button_primary.xml (主要按鈕樣式)、bg_button_outline.xml (外框按鈕樣式)、bg_input_modern.xml (現代化輸入框背景)。
6. 新增 values-sw720dp/dimens.xml:針對 720dp 以上大螢幕(10吋機台)的適配尺寸規格。
7. 新增主控台各功能示意圖資源(drawable/*.png):binding_machine、card_reader_checkout、card_reader_reboot、cargo_lane_management、product_list、reboot_app、sales_page、scanner_test、system_settings、taiwan_star_tech_logo、view_log_records、vending_machine_scenario_v1/v2。
8. app/build.gradle:verPatch 從 1 升至 6,對應本次多功能迭代開發。
9. dialog_admin_login.xml:新增獨立的管理員登入對話框佈局。
This commit is contained in:
sky121113 2026-05-27 17:15:07 +08:00
parent 3409f93f07
commit 984bbe722c
23 changed files with 1153 additions and 355 deletions

View File

@ -10,7 +10,7 @@ plugins {
// =========================================================================
def verMajor = 10
def verMinor = 1
def verPatch = 1
def verPatch = 6
android {
namespace 'com.unibuy.smartdevice'

View File

@ -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);
}
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 背景透明 -->
<solid android:color="@color/transparent" />
<!-- 外框線設定 -->
<stroke
android:width="2dp"
android:color="@color/outline" />
<!-- 圓角設定 (與輸入框 bg_input_modern 的 12dp 圓角一致) -->
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 主色背景 -->
<solid android:color="@color/primary" />
<!-- 圓角設定 (與輸入框 bg_input_modern 的 12dp 圓角一致) -->
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/surface" />
<stroke android:width="1.5dp" android:color="@color/outlineVariant" />
<corners android:radius="12dp" />
<padding
android:left="16dp"
android:top="12dp"
android:right="16dp"
android:bottom="12dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
<?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="wrap_content"
android:orientation="vertical"
android:padding="36dp"
android:background="@color/surface">
<!-- 標題 -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登入後台管理帳號"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@color/onSurface"
android:layout_marginBottom="24dp" />
<!-- 帳號標籤 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="管理員帳號"
android:textSize="22sp"
android:textColor="@color/onSurfaceVariant"
android:layout_marginBottom="8dp"
android:layout_marginStart="4dp" />
<!-- 帳號輸入框 -->
<EditText
android:id="@+id/etAccess"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入帳號"
android:textSize="24sp"
android:background="@drawable/bg_input_modern"
android:singleLine="true"
android:padding="16dp"
android:layout_marginBottom="24dp" />
<!-- 密碼標籤 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="管理員密碼"
android:textSize="22sp"
android:textColor="@color/onSurfaceVariant"
android:layout_marginBottom="8dp"
android:layout_marginStart="4dp" />
<!-- 密碼輸入框 -->
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入密碼"
android:textSize="24sp"
android:background="@drawable/bg_input_modern"
android:inputType="textPassword"
android:singleLine="true"
android:padding="16dp"
android:layout_marginBottom="20dp" />
<!-- 記住帳號 -->
<CheckBox
android:id="@+id/cbRemember"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="記住帳號"
android:textSize="22sp"
android:textColor="@color/onSurfaceVariant"
android:layout_marginBottom="28dp" />
<!-- 按鈕區塊 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal">
<!-- 取消按鈕 -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_outline"
android:text="取消"
android:textSize="22sp"
android:textColor="@color/outline"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:layout_marginEnd="20dp"
android:textAllCaps="false" />
<!-- 登入按鈕 -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_primary"
android:text="登入"
android:textSize="22sp"
android:textColor="@color/white"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default screen margins for large screens -->
<dimen name="activity_horizontal_margin">24dp</dimen>
<dimen name="activity_vertical_margin">24dp</dimen>
<dimen name="fab_margin">24dp</dimen>
<dimen name="bottom_nav_icon_size">68dp</dimen>
<dimen name="bottom_navigation_height">165dp</dimen>
<!-- 歡迎標題與登入區 (放大約 1.5 倍) -->
<dimen name="welcome_title_text_size">26sp</dimen>
<dimen name="welcome_hint_text_size">22sp</dimen>
<dimen name="login_btn_text_size">28sp</dimen>
<dimen name="header_logo_width">360dp</dimen>
<dimen name="header_logo_height">255dp</dimen>
<!-- 群組標題 (放大約 1.5 倍) -->
<dimen name="group_title_text_size">26sp</dimen>
<!-- 選單功能按鈕 (放大約 1.5 倍) -->
<dimen name="menu_btn_text_size">26sp</dimen>
<dimen name="menu_btn_icon_size">108dp</dimen>
<dimen name="menu_btn_inset">12dp</dimen>
<dimen name="menu_btn_corner_radius">24dp</dimen>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="black">#272727</color>
<color name="white">#FFFFFFFF</color>
<color name="red">#FFFF0000</color>
<color name="light_gray">#FFA9A9A9</color>
@ -8,4 +8,57 @@
<color name="transparent_black">#AA000000</color>
<color name="transparent_white">#80FFFFFF</color>
<color name="transparent">#00000000</color>
<!-- 全域系統色彩系統 (Material 3 標準色系 - 沉穩藍視角) -->
<!-- Primary 主色 (#005DAD) -->
<color name="primary">#005DAD</color>
<color name="onPrimary">#FFFFFF</color>
<color name="primaryContainer">#D1E4FF</color>
<color name="onPrimaryContainer">#001D36</color>
<!-- Secondary 次色 (藍灰色調) -->
<color name="secondary">#535F70</color>
<color name="onSecondary">#FFFFFF</color>
<color name="secondaryContainer">#D7E3F7</color>
<color name="onSecondaryContainer">#101C2B</color>
<!-- Semantic: Success 成功狀態 -->
<color name="success">#2E7D32</color>
<color name="onSuccess">#FFFFFF</color>
<color name="successContainer">#C8E6C9</color>
<color name="onSuccessContainer">#003300</color>
<!-- Semantic: Warning 警告狀態 -->
<color name="warning">#EF6C00</color>
<color name="onWarning">#FFFFFF</color>
<color name="warningContainer">#FFE0B2</color>
<color name="onWarningContainer">#4A2600</color>
<!-- Semantic: Error / Dangerous 錯誤與危險狀態 -->
<color name="error">#BA1A1A</color>
<color name="onError">#FFFFFF</color>
<color name="errorContainer">#FFDAD6</color>
<color name="onErrorContainer">#410002</color>
<!-- Semantic: Info 提示狀態 -->
<color name="info">#00639B</color>
<color name="onInfo">#FFFFFF</color>
<color name="infoContainer">#D1E4FF</color>
<color name="onInfoContainer">#001D35</color>
<!-- Background & Surface 背景與表面 (調整為微冷調白) -->
<color name="paperColor">#F5F7FA</color>
<color name="surface">#F8F9FF</color>
<color name="onSurface">#191C1E</color>
<color name="surfaceVariant">#DFE2EB</color>
<color name="onSurfaceVariant">#43474E</color>
<color name="background">#F8F9FF</color>
<color name="onBackground">#191C1E</color>
<!-- Outlines & Dividers 邊框與分隔線 -->
<color name="outline">#73777F</color>
<color name="outlineVariant">#C3C7CF</color>
<color name="divider">#DFE2EB</color>
</resources>

View File

@ -5,4 +5,20 @@
<dimen name="fab_margin">16dp</dimen>
<dimen name="bottom_nav_icon_size">45dp</dimen>
<dimen name="bottom_navigation_height">110dp</dimen>
<!-- 歡迎標題與登入區 -->
<dimen name="welcome_title_text_size">18sp</dimen>
<dimen name="welcome_hint_text_size">16sp</dimen>
<dimen name="login_btn_text_size">20sp</dimen>
<dimen name="header_logo_width">240dp</dimen>
<dimen name="header_logo_height">170dp</dimen>
<!-- 群組標題 -->
<dimen name="group_title_text_size">18sp</dimen>
<!-- 選單功能按鈕 -->
<dimen name="menu_btn_text_size">18sp</dimen>
<dimen name="menu_btn_icon_size">72dp</dimen>
<dimen name="menu_btn_inset">8dp</dimen>
<dimen name="menu_btn_corner_radius">16dp</dimen>
</resources>