4 changed files with 781 additions and 21 deletions
-
114app/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