4 changed files with 781 additions and 21 deletions
-
110app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java
-
68app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java
-
412app/src/main/java/com/ouxuan/oxface/device/UnlockPasswordDialog.java
-
208解锁密码弹窗功能实现说明.md
@ -0,0 +1,412 @@ |
|||
package com.ouxuan.oxface.device; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.graphics.Color; |
|||
import android.graphics.drawable.ColorDrawable; |
|||
import android.text.InputType; |
|||
import android.view.Gravity; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.view.Window; |
|||
import android.view.WindowManager; |
|||
import android.widget.Button; |
|||
import android.widget.EditText; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import com.ouxuan.oxface.data.LoginDataManager; |
|||
import com.ouxuan.oxface.utils.LogManager; |
|||
|
|||
/** |
|||
* 解锁密码弹窗 |
|||
* 用于特殊情况下退出人脸识别界面 |
|||
*/ |
|||
public class UnlockPasswordDialog { |
|||
|
|||
private static final String TAG = "UnlockPasswordDialog"; |
|||
|
|||
private Context context; |
|||
private Dialog dialog; |
|||
private EditText etPassword; |
|||
private TextView tvErrorTip; |
|||
private Button btnConfirm, btnCancel; |
|||
private LoginDataManager loginDataManager; |
|||
private UnlockDialogListener listener; |
|||
|
|||
/** |
|||
* 解锁弹窗监听器 |
|||
*/ |
|||
public interface UnlockDialogListener { |
|||
/** |
|||
* 解锁成功回调 |
|||
*/ |
|||
void onUnlockSuccess(); |
|||
|
|||
/** |
|||
* 弹窗显示时回调(暂停摄像头) |
|||
*/ |
|||
void onDialogShow(); |
|||
|
|||
/** |
|||
* 弹窗关闭时回调(恢复摄像头) |
|||
*/ |
|||
void onDialogDismiss(); |
|||
} |
|||
|
|||
public UnlockPasswordDialog(Context context) { |
|||
this.context = context; |
|||
this.loginDataManager = LoginDataManager.getInstance(context); |
|||
createDialog(); |
|||
} |
|||
|
|||
/** |
|||
* 设置监听器 |
|||
* @param listener 监听器 |
|||
*/ |
|||
public void setListener(UnlockDialogListener listener) { |
|||
this.listener = listener; |
|||
} |
|||
|
|||
/** |
|||
* 创建弹窗 |
|||
*/ |
|||
private void createDialog() { |
|||
dialog = new Dialog(context); |
|||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
|||
|
|||
// 创建主布局 |
|||
LinearLayout mainLayout = new LinearLayout(context); |
|||
mainLayout.setOrientation(LinearLayout.VERTICAL); |
|||
mainLayout.setBackgroundColor(Color.parseColor("#CC000000")); // 半透明黑色背景 |
|||
mainLayout.setGravity(Gravity.CENTER); |
|||
mainLayout.setPadding(40, 40, 40, 40); |
|||
|
|||
// 内容容器 |
|||
LinearLayout contentLayout = new LinearLayout(context); |
|||
contentLayout.setOrientation(LinearLayout.VERTICAL); |
|||
contentLayout.setBackgroundColor(Color.WHITE); |
|||
contentLayout.setPadding(40, 30, 40, 30); |
|||
contentLayout.setGravity(Gravity.CENTER); |
|||
|
|||
// 设置圆角效果 |
|||
android.graphics.drawable.GradientDrawable drawable = new android.graphics.drawable.GradientDrawable(); |
|||
drawable.setColor(Color.WHITE); |
|||
drawable.setCornerRadius(20); |
|||
contentLayout.setBackground(drawable); |
|||
|
|||
// 标题 |
|||
TextView tvTitle = new TextView(context); |
|||
tvTitle.setText("解锁验证"); |
|||
tvTitle.setTextSize(20); |
|||
tvTitle.setTextColor(Color.parseColor("#333333")); |
|||
tvTitle.setGravity(Gravity.CENTER); |
|||
tvTitle.setPadding(0, 0, 0, 20); |
|||
|
|||
// 提示文字 |
|||
TextView tvHint = new TextView(context); |
|||
tvHint.setText("请输入登录密码以退出人脸识别"); |
|||
tvHint.setTextSize(14); |
|||
tvHint.setTextColor(Color.parseColor("#666666")); |
|||
tvHint.setGravity(Gravity.CENTER); |
|||
tvHint.setPadding(0, 0, 0, 15); |
|||
|
|||
// 密码输入框 |
|||
etPassword = new EditText(context); |
|||
etPassword.setHint("请输入密码"); |
|||
etPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); |
|||
etPassword.setTextSize(16); |
|||
etPassword.setTextColor(Color.parseColor("#333333")); |
|||
etPassword.setHintTextColor(Color.parseColor("#999999")); |
|||
etPassword.setPadding(20, 15, 20, 15); |
|||
etPassword.setBackgroundColor(Color.parseColor("#F5F5F5")); |
|||
|
|||
// 设置输入框圆角 |
|||
android.graphics.drawable.GradientDrawable inputDrawable = new android.graphics.drawable.GradientDrawable(); |
|||
inputDrawable.setColor(Color.parseColor("#F5F5F5")); |
|||
inputDrawable.setCornerRadius(10); |
|||
inputDrawable.setStroke(2, Color.parseColor("#E0E0E0")); |
|||
etPassword.setBackground(inputDrawable); |
|||
|
|||
LinearLayout.LayoutParams inputParams = new LinearLayout.LayoutParams( |
|||
(int)(280 * context.getResources().getDisplayMetrics().density), |
|||
LinearLayout.LayoutParams.WRAP_CONTENT |
|||
); |
|||
inputParams.setMargins(0, 0, 0, 10); |
|||
etPassword.setLayoutParams(inputParams); |
|||
|
|||
// 错误提示 |
|||
tvErrorTip = new TextView(context); |
|||
tvErrorTip.setTextSize(12); |
|||
tvErrorTip.setTextColor(Color.parseColor("#FF4444")); |
|||
tvErrorTip.setGravity(Gravity.CENTER); |
|||
tvErrorTip.setVisibility(View.GONE); |
|||
tvErrorTip.setPadding(0, 0, 0, 15); |
|||
|
|||
// 按钮容器 |
|||
LinearLayout buttonLayout = new LinearLayout(context); |
|||
buttonLayout.setOrientation(LinearLayout.HORIZONTAL); |
|||
buttonLayout.setGravity(Gravity.CENTER); |
|||
buttonLayout.setPadding(0, 20, 0, 0); |
|||
|
|||
// 取消按钮 |
|||
btnCancel = new Button(context); |
|||
btnCancel.setText("取消"); |
|||
btnCancel.setTextSize(16); |
|||
btnCancel.setTextColor(Color.parseColor("#666666")); |
|||
btnCancel.setBackgroundColor(Color.parseColor("#F0F0F0")); |
|||
|
|||
// 设置取消按钮圆角 |
|||
android.graphics.drawable.GradientDrawable cancelDrawable = new android.graphics.drawable.GradientDrawable(); |
|||
cancelDrawable.setColor(Color.parseColor("#F0F0F0")); |
|||
cancelDrawable.setCornerRadius(8); |
|||
btnCancel.setBackground(cancelDrawable); |
|||
|
|||
LinearLayout.LayoutParams cancelParams = new LinearLayout.LayoutParams( |
|||
(int)(100 * context.getResources().getDisplayMetrics().density), |
|||
(int)(45 * context.getResources().getDisplayMetrics().density) |
|||
); |
|||
cancelParams.setMargins(0, 0, 20, 0); |
|||
btnCancel.setLayoutParams(cancelParams); |
|||
|
|||
// 确认按钮 |
|||
btnConfirm = new Button(context); |
|||
btnConfirm.setText("确认"); |
|||
btnConfirm.setTextSize(16); |
|||
btnConfirm.setTextColor(Color.WHITE); |
|||
btnConfirm.setBackgroundColor(Color.parseColor("#007AFF")); |
|||
|
|||
// 设置确认按钮圆角 |
|||
android.graphics.drawable.GradientDrawable confirmDrawable = new android.graphics.drawable.GradientDrawable(); |
|||
confirmDrawable.setColor(Color.parseColor("#007AFF")); |
|||
confirmDrawable.setCornerRadius(8); |
|||
btnConfirm.setBackground(confirmDrawable); |
|||
|
|||
LinearLayout.LayoutParams confirmParams = new LinearLayout.LayoutParams( |
|||
(int)(100 * context.getResources().getDisplayMetrics().density), |
|||
(int)(45 * context.getResources().getDisplayMetrics().density) |
|||
); |
|||
btnConfirm.setLayoutParams(confirmParams); |
|||
|
|||
// 添加按钮到容器 |
|||
buttonLayout.addView(btnCancel); |
|||
buttonLayout.addView(btnConfirm); |
|||
|
|||
// 添加所有视图到内容容器 |
|||
contentLayout.addView(tvTitle); |
|||
contentLayout.addView(tvHint); |
|||
contentLayout.addView(etPassword); |
|||
contentLayout.addView(tvErrorTip); |
|||
contentLayout.addView(buttonLayout); |
|||
|
|||
// 设置内容布局参数 |
|||
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( |
|||
(int)(320 * context.getResources().getDisplayMetrics().density), |
|||
LinearLayout.LayoutParams.WRAP_CONTENT |
|||
); |
|||
contentParams.gravity = Gravity.CENTER; |
|||
mainLayout.addView(contentLayout, contentParams); |
|||
|
|||
// 设置Dialog属性 |
|||
dialog.setContentView(mainLayout); |
|||
|
|||
Window window = dialog.getWindow(); |
|||
if (window != null) { |
|||
// 设置全屏显示 |
|||
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); |
|||
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); |
|||
|
|||
// 设置窗口标志 |
|||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
|||
|
|||
// 清除默认动画 |
|||
window.setWindowAnimations(0); |
|||
} |
|||
|
|||
// 设置不可取消 |
|||
dialog.setCancelable(false); |
|||
dialog.setCanceledOnTouchOutside(false); |
|||
|
|||
// 设置监听器 |
|||
setupListeners(); |
|||
} |
|||
|
|||
/** |
|||
* 设置监听器 |
|||
*/ |
|||
private void setupListeners() { |
|||
// 取消按钮 |
|||
btnCancel.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
LogManager.logInfo(TAG, "用户取消解锁"); |
|||
dismiss(); |
|||
} |
|||
}); |
|||
|
|||
// 确认按钮 |
|||
btnConfirm.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
handleConfirm(); |
|||
} |
|||
}); |
|||
|
|||
// 密码输入框回车监听 |
|||
etPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() { |
|||
@Override |
|||
public boolean onEditorAction(TextView v, int actionId, android.view.KeyEvent event) { |
|||
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_DONE || |
|||
actionId == android.view.inputmethod.EditorInfo.IME_ACTION_GO || |
|||
(event != null && event.getKeyCode() == android.view.KeyEvent.KEYCODE_ENTER)) { |
|||
handleConfirm(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 处理确认按钮点击 |
|||
*/ |
|||
private void handleConfirm() { |
|||
String inputPassword = etPassword.getText().toString().trim(); |
|||
|
|||
if (inputPassword.isEmpty()) { |
|||
showErrorTip("请输入密码"); |
|||
return; |
|||
} |
|||
|
|||
// 获取保存的登录密码 |
|||
String savedPassword = loginDataManager.getPassword(); |
|||
|
|||
if (savedPassword == null || savedPassword.isEmpty()) { |
|||
LogManager.logError(TAG, "未找到保存的登录密码"); |
|||
showErrorTip("系统错误:未找到登录密码"); |
|||
return; |
|||
} |
|||
|
|||
// 验证密码 |
|||
if (inputPassword.equals(savedPassword)) { |
|||
LogManager.logInfo(TAG, "密码验证成功,准备解锁退出"); |
|||
hideErrorTip(); |
|||
|
|||
// 通知解锁成功 |
|||
if (listener != null) { |
|||
listener.onUnlockSuccess(); |
|||
} |
|||
|
|||
dismiss(); |
|||
} else { |
|||
LogManager.logWarning(TAG, "密码验证失败"); |
|||
showErrorTip("密码错误,请重新输入"); |
|||
|
|||
// 清空输入框 |
|||
etPassword.setText(""); |
|||
etPassword.requestFocus(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 显示错误提示 |
|||
* @param message 错误信息 |
|||
*/ |
|||
private void showErrorTip(String message) { |
|||
tvErrorTip.setText(message); |
|||
tvErrorTip.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
/** |
|||
* 隐藏错误提示 |
|||
*/ |
|||
private void hideErrorTip() { |
|||
tvErrorTip.setVisibility(View.GONE); |
|||
} |
|||
|
|||
/** |
|||
* 显示弹窗 |
|||
*/ |
|||
public void show() { |
|||
try { |
|||
if (dialog != null && !dialog.isShowing()) { |
|||
// 清空之前的输入和错误提示 |
|||
etPassword.setText(""); |
|||
hideErrorTip(); |
|||
|
|||
// 显示弹窗 |
|||
dialog.show(); |
|||
|
|||
// 请求输入框焦点 |
|||
etPassword.requestFocus(); |
|||
|
|||
// 显示软键盘 |
|||
android.view.inputmethod.InputMethodManager imm = |
|||
(android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); |
|||
if (imm != null) { |
|||
imm.showSoftInput(etPassword, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); |
|||
} |
|||
|
|||
LogManager.logInfo(TAG, "解锁密码弹窗已显示"); |
|||
|
|||
// 通知监听器弹窗显示 |
|||
if (listener != null) { |
|||
listener.onDialogShow(); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
LogManager.logError(TAG, "显示解锁密码弹窗失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关闭弹窗 |
|||
*/ |
|||
public void dismiss() { |
|||
try { |
|||
if (dialog != null && dialog.isShowing()) { |
|||
// 隐藏软键盘 |
|||
android.view.inputmethod.InputMethodManager imm = |
|||
(android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); |
|||
if (imm != null) { |
|||
imm.hideSoftInputFromWindow(etPassword.getWindowToken(), 0); |
|||
} |
|||
|
|||
dialog.dismiss(); |
|||
LogManager.logInfo(TAG, "解锁密码弹窗已关闭"); |
|||
|
|||
// 通知监听器弹窗关闭 |
|||
if (listener != null) { |
|||
listener.onDialogDismiss(); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
LogManager.logError(TAG, "关闭解锁密码弹窗失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查弹窗是否正在显示 |
|||
* @return true表示正在显示 |
|||
*/ |
|||
public boolean isShowing() { |
|||
return dialog != null && dialog.isShowing(); |
|||
} |
|||
|
|||
/** |
|||
* 释放资源 |
|||
*/ |
|||
public void release() { |
|||
dismiss(); |
|||
|
|||
if (dialog != null) { |
|||
dialog = null; |
|||
} |
|||
|
|||
context = null; |
|||
listener = null; |
|||
loginDataManager = null; |
|||
|
|||
LogManager.logInfo(TAG, "解锁密码弹窗资源已释放"); |
|||
} |
|||
} |
@ -0,0 +1,208 @@ |
|||
# 解锁密码弹窗功能实现说明 |
|||
|
|||
## 功能概述 |
|||
|
|||
本次实现了解锁密码弹窗功能,为特殊情况下退出人脸识别界面提供了一个安全的出口。该功能完全符合用户需求,提供了美观简洁的界面和安全的密码验证机制。 |
|||
|
|||
## 实现的功能 |
|||
|
|||
### 1. 解锁密码弹窗类 (`UnlockPasswordDialog.java`) |
|||
|
|||
**位置**: `app/src/main/java/com/ouxuan/oxface/device/UnlockPasswordDialog.java` |
|||
|
|||
**主要特性**: |
|||
- 美观的弹窗界面设计,符合主题色 |
|||
- 安全的密码输入(显示为密码形式) |
|||
- 实时密码验证(与本地存储的登录密码对比) |
|||
- 错误提示功能(密码错误时显示红色提示) |
|||
- 摄像头控制集成(弹窗显示时暂停摄像头,关闭时恢复) |
|||
- 软键盘自动弹出和收起 |
|||
- 完整的生命周期管理 |
|||
|
|||
**核心方法**: |
|||
- `show()`: 显示弹窗 |
|||
- `dismiss()`: 关闭弹窗 |
|||
- `handleConfirm()`: 处理密码验证 |
|||
- `UnlockDialogListener`: 回调接口 |
|||
|
|||
### 2. 触发机制 |
|||
|
|||
#### 2.1 店铺名称连续点击触发 |
|||
**位置**: `OXFaceOnlineActivity.java` 中的 `handleStoreNameClick()` 方法 |
|||
|
|||
**触发条件**: 连续6次点击视频流界面左上角的店铺名称按钮(`tv_store_name`) |
|||
- 点击间隔限制:1秒内点击有效 |
|||
- 点击计数:达到6次时触发 |
|||
- 用户提示:3次点击后提示剩余次数 |
|||
|
|||
#### 2.2 门禁不可用弹窗标题点击触发 |
|||
**位置**: `GateUnavailableDialog.java` 中的 `handleTitleClick()` 方法 |
|||
|
|||
**触发条件**: 连续6次点击门禁不可用弹窗的标题(`tvTitle`) |
|||
- 点击间隔限制:1秒内点击有效 |
|||
- 点击计数:达到6次时触发 |
|||
- 日志记录:详细记录点击过程 |
|||
|
|||
### 3. 密码验证机制 |
|||
|
|||
**验证逻辑**: |
|||
1. 获取用户输入的密码 |
|||
2. 从 `LoginDataManager` 获取本地存储的登录密码 |
|||
3. 进行字符串比较验证 |
|||
4. 验证成功:调用 `unLockAndLeaveFaceDetect()` 方法 |
|||
5. 验证失败:显示红色错误提示,清空输入框 |
|||
|
|||
**安全特性**: |
|||
- 密码以密文形式显示 |
|||
- 错误时提供明确的用户反馈 |
|||
- 支持键盘回车确认 |
|||
|
|||
### 4. 解锁退出功能 (`unLockAndLeaveFaceDetect()`) |
|||
|
|||
**执行流程**: |
|||
1. 停止摄像头预览 |
|||
2. 创建返回登录界面的Intent |
|||
3. 设置Intent标志(清除任务栈) |
|||
4. 启动MainActivity(登录界面) |
|||
5. 结束当前人脸识别界面 |
|||
6. 记录详细的操作日志 |
|||
|
|||
### 5. 摄像头控制集成 |
|||
|
|||
**自动控制**: |
|||
- 弹窗显示时:自动暂停摄像头预览 |
|||
- 弹窗关闭时:自动恢复摄像头预览 |
|||
- 解锁成功时:永久停止摄像头(准备退出) |
|||
|
|||
## 界面设计特点 |
|||
|
|||
### 1. 视觉设计 |
|||
- **主题色配色**: 蓝色确认按钮 (#007AFF),灰色取消按钮 |
|||
- **圆角设计**: 弹窗、按钮、输入框均采用圆角设计 |
|||
- **半透明背景**: 黑色半透明背景 (#CC000000) |
|||
- **响应式布局**: 根据屏幕密度自适应大小 |
|||
|
|||
### 2. 交互设计 |
|||
- **焦点管理**: 弹窗显示时自动聚焦到密码输入框 |
|||
- **键盘控制**: 自动弹出软键盘,支持回车确认 |
|||
- **错误反馈**: 密码错误时显示红色提示文字 |
|||
- **防误操作**: 弹窗不可通过点击外部区域关闭 |
|||
|
|||
### 3. 尺寸规格 |
|||
- **弹窗宽度**: 320dp |
|||
- **输入框宽度**: 280dp |
|||
- **按钮尺寸**: 100dp × 45dp |
|||
- **文字大小**: 标题20sp,正文16sp,提示14sp |
|||
|
|||
## 技术实现细节 |
|||
|
|||
### 1. 类结构设计 |
|||
```java |
|||
// 主要类和接口 |
|||
- UnlockPasswordDialog: 主弹窗类 |
|||
- UnlockDialogListener: 回调接口 |
|||
- UnlockPasswordDialogTrigger: 触发器接口 |
|||
``` |
|||
|
|||
### 2. 生命周期管理 |
|||
- 创建时初始化所有UI组件 |
|||
- 显示时设置焦点和键盘 |
|||
- 关闭时清理资源和隐藏键盘 |
|||
- 销毁时释放所有引用 |
|||
|
|||
### 3. 错误处理 |
|||
- 完善的异常捕获和日志记录 |
|||
- 用户友好的错误提示 |
|||
- 安全的资源释放机制 |
|||
|
|||
## 配置和依赖 |
|||
|
|||
### 1. 依赖的现有组件 |
|||
- `LoginDataManager`: 获取存储的登录密码 |
|||
- `LogManager`: 日志记录 |
|||
- `CameraPreviewManager`: 摄像头控制 |
|||
|
|||
### 2. 权限要求 |
|||
- 无需额外权限 |
|||
- 使用现有的摄像头权限 |
|||
|
|||
## 使用方法 |
|||
|
|||
### 1. 普通用户使用 |
|||
1. 在人脸识别界面,连续快速点击左上角店铺名称6次 |
|||
2. 或者等待门禁不可用弹窗出现时,连续快速点击弹窗标题6次 |
|||
3. 在弹出的解锁密码弹窗中输入登录密码 |
|||
4. 点击确认或按回车键 |
|||
5. 密码正确后自动返回登录界面 |
|||
|
|||
### 2. 开发者调试 |
|||
- 查看日志中的 "解锁" 相关信息 |
|||
- 验证点击计数和密码验证逻辑 |
|||
- 检查摄像头暂停/恢复状态 |
|||
|
|||
## 安全性考虑 |
|||
|
|||
### 1. 访问控制 |
|||
- 隐蔽的触发方式(连续6次点击) |
|||
- 密码验证机制 |
|||
- 无明显的UI提示(防止误操作) |
|||
|
|||
### 2. 密码安全 |
|||
- 使用现有的登录密码验证 |
|||
- 密文显示输入内容 |
|||
- 错误时不显示具体的密码信息 |
|||
|
|||
### 3. 日志安全 |
|||
- 不记录用户输入的密码 |
|||
- 只记录验证成功/失败的结果 |
|||
- 详细记录操作流程便于调试 |
|||
|
|||
## 测试建议 |
|||
|
|||
### 1. 功能测试 |
|||
- 测试两种触发方式 |
|||
- 测试正确和错误密码的处理 |
|||
- 测试摄像头暂停/恢复功能 |
|||
- 测试解锁后的界面跳转 |
|||
|
|||
### 2. 界面测试 |
|||
- 不同屏幕尺寸的适配 |
|||
- 横竖屏切换(如支持) |
|||
- 软键盘弹出和收起 |
|||
- 错误提示的显示和隐藏 |
|||
|
|||
### 3. 异常测试 |
|||
- 密码为空的处理 |
|||
- 网络异常时的处理 |
|||
- 内存不足时的处理 |
|||
- 快速重复操作的处理 |
|||
|
|||
## 维护说明 |
|||
|
|||
### 1. 常见问题排查 |
|||
- 检查 `LoginDataManager` 中是否有保存的密码 |
|||
- 确认点击计数逻辑是否正确 |
|||
- 验证摄像头控制广播是否正常 |
|||
|
|||
### 2. 代码修改建议 |
|||
- 若需修改触发次数,修改 `MAX_CLICK_COUNT` 和 `TITLE_MAX_CLICK_COUNT` 常量 |
|||
- 若需修改UI样式,在 `createDialog()` 方法中调整 |
|||
- 若需修改密码验证逻辑,在 `handleConfirm()` 方法中调整 |
|||
|
|||
### 3. 日志查看 |
|||
- 搜索 "解锁" 关键字查看相关日志 |
|||
- 搜索 "点击" 关键字查看触发日志 |
|||
- 搜索 "密码" 关键字查看验证日志(不包含实际密码) |
|||
|
|||
## 总结 |
|||
|
|||
本实现完全满足了用户的需求: |
|||
✅ 两种隐蔽的触发方式(店铺名称和弹窗标题) |
|||
✅ 美观简洁的弹窗界面 |
|||
✅ 安全的密码验证机制 |
|||
✅ 完整的摄像头控制集成 |
|||
✅ 可靠的解锁退出功能 |
|||
✅ 符合主题色的界面设计 |
|||
✅ 完善的错误处理和日志记录 |
|||
|
|||
该功能为特殊情况下的程序退出提供了安全可靠的解决方案,既保证了系统的安全性,又提供了良好的用户体验。 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue