Merge pull request '扩展功能' (#15) from jiangtianxiang_branch into master
commit
c62a941b26
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,18 @@
|
||||
# Fix NullPointerException in NoteInfoAdapter
|
||||
|
||||
The crash on startup is caused by a `NullPointerException` in `NoteInfoAdapter.java`. The `typeIcon` field in the `ViewHolder` is declared but never initialized in the `getView` method, leading to a crash when the code attempts to access `holder.typeIcon.setVisibility(...)` or `holder.typeIcon.setImageResource(...)`.
|
||||
|
||||
## Changes to `app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java`
|
||||
|
||||
1. **Initialize `typeIcon`**: In the `getView` method, inside the `if (convertView == null)` block, add the initialization for `holder.typeIcon`.
|
||||
|
||||
```java
|
||||
// Inside getView method, if (convertView == null) block:
|
||||
holder.title = convertView.findViewById(R.id.tv_title);
|
||||
holder.time = convertView.findViewById(R.id.tv_time);
|
||||
holder.typeIcon = convertView.findViewById(R.id.iv_type_icon); // Add this line
|
||||
holder.checkBox = convertView.findViewById(android.R.id.checkbox);
|
||||
holder.pinnedIcon = convertView.findViewById(R.id.iv_pinned_icon);
|
||||
```
|
||||
|
||||
This simple fix will resolve the runtime crash and allow the application to start correctly, displaying the folder/note icons as intended.
|
||||
@ -0,0 +1,36 @@
|
||||
I have identified the issue: `PasswordActivity` does not properly handle the "Setup Password" flow. When the user selects a password type (PIN or Pattern), `NotesPreferenceActivity` launches `PasswordActivity`, but `PasswordActivity` might be crashing or finishing immediately due to logic errors, causing the app to fall back to the main activity.
|
||||
|
||||
Here is the plan to fix the issue and fully implement the password setup flow:
|
||||
|
||||
1. **Modify** **`PasswordActivity.java`**:
|
||||
|
||||
* Ensure `onCreate` correctly initializes the UI based on `ACTION_SETUP_PASSWORD` or `ACTION_CHECK_PASSWORD`.
|
||||
|
||||
* In `setupViews`, verify that the correct input view (`EditText` for PIN, `LockPatternView` for Pattern) is made visible and others are hidden.
|
||||
|
||||
* Review the `handleInput` logic for the setup phase (`mMode == 1`). It needs to correctly handle the two-step confirmation (first input -> prompt confirmation -> second input -> save).
|
||||
|
||||
* Add proper error handling and user feedback (Toast messages) if inputs don't match.
|
||||
|
||||
2. **Modify** **`NotesPreferenceActivity.java`**:
|
||||
|
||||
* Verify the `showSetPasswordDialog` correctly passes the `EXTRA_PASSWORD_TYPE` intent extra.
|
||||
|
||||
* Ensure `onActivityResult` correctly handles the result from `PasswordActivity` (e.g., refreshing the UI or showing a success message).
|
||||
|
||||
3. **Resource Check**:
|
||||
|
||||
* Ensure `activity_password.xml` contains all necessary views with correct IDs (`et_pin`, `lock_pattern_view`, `tv_prompt`, `tv_error`, `btn_cancel`).
|
||||
|
||||
4. **Verification**:
|
||||
|
||||
* Compile and run the app.
|
||||
|
||||
* Go to Settings -> Security Settings.
|
||||
|
||||
* Select "Digital Lock" (PIN). Verify the PIN entry screen appears. Enter a PIN twice. Verify success.
|
||||
|
||||
* Select "Gesture Lock" (Pattern). Verify the pattern view appears. Draw a pattern twice. Verify success.
|
||||
|
||||
* After setting a password, click Security Settings again. Verify it prompts for the old password before allowing changes.
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
I have identified the causes for the "Security Settings" unresponsiveness and the incomplete implementation of the Privacy Lock feature. Here is the plan to fix these issues and fully implement the feature.
|
||||
|
||||
### 1. Fix "Security Settings" Unresponsiveness
|
||||
- **File**: `app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java`
|
||||
- **Issue**: The `loadSecurityPreference()` method is defined but never called.
|
||||
- **Action**: Add a call to `loadSecurityPreference()` inside the `onCreate()` method.
|
||||
|
||||
### 2. Implement Lock Icon Display in List
|
||||
- **File**: `app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java`
|
||||
- **Issue**: The lock icon (`lockIcon`) is defined in the ViewHolder but its visibility is never set in `getView()`.
|
||||
- **Action**: Add logic in `getView()` to set `holder.lockIcon` visibility to `VISIBLE` if `note.isLocked` is true, otherwise `GONE`.
|
||||
|
||||
### 3. Implement Password Protection Logic
|
||||
- **File**: `app/src/main/java/net/micode/notes/ui/NotesListActivity.java`
|
||||
- **Issue**: Clicking on a locked note or folder currently opens it directly, bypassing any security check.
|
||||
- **Action**:
|
||||
- Modify `handleItemClick` (and/or `onNoteItemClick`) to check `note.isLocked`.
|
||||
- If locked, launch `PasswordActivity` with `ACTION_CHECK_PASSWORD`.
|
||||
- Store the target note in `mPendingNoteToOpen`.
|
||||
- Implement `onActivityResult` to handle the success result from `PasswordActivity` and proceed to open the note/folder.
|
||||
|
||||
### 4. Verification
|
||||
- **Compile**: Ensure all changes compile without errors.
|
||||
- **Test**:
|
||||
- Verify clicking "Security Settings" now opens the password setup dialog.
|
||||
- Verify locking a note shows the lock icon.
|
||||
- Verify clicking a locked note prompts for a password.
|
||||
- Verify entering the correct password opens the note.
|
||||
@ -0,0 +1,30 @@
|
||||
# Update Toolbar Title and Menu Handling in NotesListActivity
|
||||
|
||||
To address the user's request of changing the toolbar title to "Trash" when entering the Trash folder and back to "Notes" when returning, I will modify `NotesListActivity.java`.
|
||||
|
||||
The current implementation only updates the toolbar title during initialization and mode switching (Multi-select/Normal), but not when navigating between folders.
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### `app/src/main/java/net/micode/notes/ui/NotesListActivity.java`
|
||||
|
||||
1. **Modify** **`updateToolbarForNormalMode()`**:
|
||||
|
||||
* Update the title setting logic to use `R.string.menu_trash` for the Trash folder instead of the hardcoded "回收站".
|
||||
|
||||
* Add logic to re-inflate `R.menu.note_list` when in Normal mode, as `toolbar.getMenu().clear()` removes the menu items. This ensures the menu remains visible after folder navigation.
|
||||
|
||||
2. **Update** **`observeViewModel()`**:
|
||||
|
||||
* In the `notesLiveData` observer, add a call to `updateToolbarForNormalMode()`. This ensures that whenever the note list is refreshed (which happens on folder navigation), the toolbar title and menu are updated to reflect the current state.
|
||||
|
||||
## Verification
|
||||
|
||||
After applying these changes:
|
||||
|
||||
* Navigate to "Trash" from the sidebar -> Title should update to "Trash" (from `strings.xml`).
|
||||
|
||||
* Navigate back to "Notes" -> Title should update to "Notes".
|
||||
|
||||
* The options menu (Search, New Folder, etc.) should remain visible in "Notes" mode and be hidden (or appropriate) in "Trash" mode.
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
# 图标不显示问题修复与方案改进计划
|
||||
|
||||
## 1. 问题分析
|
||||
根据用户反馈和截图:
|
||||
1. **图标未显示**: 文件夹左侧没有出现预期的图标。
|
||||
2. **时间显示异常**: 普通便签的时间也没有正确显示(可能是因为字体颜色、大小问题,或者被隐藏了)。
|
||||
3. **截图观察**: 截图显示列表项背景色正常,有标题,右侧有时间(如 "2026-01-20 09:52"),但所有项(包括置顶的“软件工程”)左侧都没有图标。置顶图标(向上箭头)出现在了右下角。
|
||||
|
||||
**潜在原因**:
|
||||
1. **`R.drawable.folder` 不存在**: 我之前假设系统资源中有 `folder`,但实际上可能没有,导致 `setImageResource` 失败或者抛出异常被捕获(虽然 `setImageResource` 不会抛异常,但会留空)。应该使用系统内置的 `android.R.drawable.ic_menu_view` 或者确保项目中有 `folder.png/xml`。
|
||||
2. **`View` 复用问题**: `ViewHolder` 模式下,如果状态重置不彻底,可能导致图标在不需要的时候显示,或者在需要的时候被隐藏。
|
||||
3. **布局属性**: `iv_type_icon` 的 `visibility` 默认为 `gone`,可能在代码中没有正确设置为 `VISIBLE`。
|
||||
4. **数据类型判断错误**: `note.type` 的值可能不是预期的 `Notes.TYPE_FOLDER`。需要确认 `Notes.TYPE_FOLDER` 的常量值。
|
||||
|
||||
## 2. 改进方案
|
||||
|
||||
### 2.1 资源确保
|
||||
* 不依赖系统不确定的 `R.drawable.folder`。
|
||||
* 使用 Android 系统自带的 **`android.R.drawable.ic_menu_view`** (通常是一个网格图标,不太像文件夹) 或者 **`android.R.drawable.ic_menu_more`**。
|
||||
* **最佳方案**: 既然是 Android Studio 环境,我们可以直接使用 Vector Asset 创建一个标准的文件夹图标 `ic_folder.xml`。如果没有,暂时使用 `android.R.drawable.ic_menu_manage` (通常是文件夹样式) 或 `android.R.drawable.star_on` (临时测试用)。为了稳妥,我将检查 `res/drawable` 目录。
|
||||
|
||||
### 2.2 代码逻辑增强
|
||||
* 在 `getView` 中增加日志,打印 `note.type` 和 `position`,确认是否进入了文件夹判断逻辑。
|
||||
* 强制设置 `holder.typeIcon.setVisibility(View.VISIBLE)` 当检测到文件夹时。
|
||||
* 对于普通便签,确保 `holder.time.setVisibility(View.VISIBLE)`。
|
||||
|
||||
### 2.3 视觉优化
|
||||
* 截图显示置顶图标在右下角,这可能是之前的修改。现在的需求是左侧区分图标。
|
||||
* **文件夹**: 左侧显示图标,右侧时间显示为 "文件夹" 或包含项数。
|
||||
* **便签**: 左侧无图标(或显示文档图标),右侧显示时间。
|
||||
|
||||
## 3. 实施步骤
|
||||
|
||||
### Step 1: 检查资源
|
||||
1. 列出 `res/drawable` 目录,看是否有合适的图标。
|
||||
2. 如果没有,我将创建一个简单的 Vector Drawable `ic_folder.xml`。
|
||||
|
||||
### Step 2: 修改 `NoteInfoAdapter.java`
|
||||
1. 修改图标资源引用:确保引用存在的资源。
|
||||
2. 增加调试日志。
|
||||
3. 修正 `getView` 逻辑:
|
||||
```java
|
||||
if (note.type == Notes.TYPE_FOLDER) {
|
||||
holder.typeIcon.setVisibility(View.VISIBLE);
|
||||
holder.typeIcon.setImageResource(R.drawable.ic_folder); // 确保此资源存在
|
||||
holder.time.setText(""); // 或者显示包含数量
|
||||
holder.time.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.typeIcon.setVisibility(View.GONE);
|
||||
holder.time.setVisibility(View.VISIBLE);
|
||||
holder.time.setText(formatDate(note.modifiedDate));
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: 验证便签时间显示
|
||||
* 截图中其实**显示了时间**(右侧灰色小字),用户说“没有显示最后修改时间”可能是指**文件夹**没有显示时间(这是符合设计的),或者是**普通便签**的时间显示不符合他的预期(比如格式不对,或者因为复用问题导致有时不显示)。
|
||||
* 我会检查 `formatDate` 方法,并确保在 `else` 分支中显式 `setVisible(VISIBLE)`。
|
||||
|
||||
## 4. 立即行动
|
||||
1. 检查 `res/drawable`。
|
||||
2. 创建 `ic_folder.xml` (如果需要)。
|
||||
3. 更新 Adapter 代码。
|
||||
@ -0,0 +1,53 @@
|
||||
# 文件夹与便签区分优化方案
|
||||
|
||||
## 1. 问题分析
|
||||
当前应用在列表中展示便签和文件夹时,可能仅通过标题和背景色区分,导致视觉上混淆,尤其当它们同名时。现有的 `note_item.xml` 布局对所有类型的项使用相同的结构,缺乏针对文件夹的特有标识。
|
||||
|
||||
## 2. 解决方案设计
|
||||
为了清晰区分文件夹和便签,我们采取以下多维度的视觉区分策略:
|
||||
|
||||
### 2.1 图标区分 (Iconography)
|
||||
* **文件夹**: 在列表项左侧添加一个明显的黄色文件夹图标。
|
||||
* **便签**: 保持现状,或者添加一个默认的文档图标(可选,保持简洁则不加)。
|
||||
|
||||
### 2.2 辅助文本 (Metadata)
|
||||
* **文件夹**: 显示该文件夹包含的笔记数量(例如 "(5)" 或 "5项")。
|
||||
* **便签**: 显示最后修改时间(现有逻辑)。
|
||||
|
||||
### 2.3 视觉样式 (Visual Style)
|
||||
* **文件夹**: 标题字体加粗,颜色略深。
|
||||
* **便签**: 标题保持常规。
|
||||
|
||||
## 3. 详细实施计划
|
||||
|
||||
### Step 1: 准备资源
|
||||
* 需要一个文件夹图标资源。可以使用系统自带的 `android.R.drawable.ic_menu_view` 或者导入一个新的 `ic_folder.xml` 矢量图。为简便起见,优先尝试系统资源,效果不佳则绘制。
|
||||
|
||||
### Step 2: 修改布局文件 (`note_item.xml`)
|
||||
* 在标题左侧添加一个 `ImageView` (`iv_type_icon`),用于显示类型图标。默认设为 `GONE` 或显示默认图标。
|
||||
* 或者复用现有的布局结构,在 `NoteInfoAdapter` 中动态控制。
|
||||
|
||||
### Step 3: 修改适配器 (`NoteInfoAdapter.java`)
|
||||
* 在 `getView` 方法中,检查 `note.type`。
|
||||
* **如果是文件夹 (`Notes.TYPE_FOLDER`)**:
|
||||
* 显示文件夹图标。
|
||||
* 在标题后追加笔记数量信息(需要 `NotesRepository` 在查询时带出 `NOTES_COUNT`,或者简单点,仅仅在视觉上加图标)。
|
||||
* 隐藏或修改时间显示(文件夹通常关注创建时间或不显示时间,显示包含数量更有用)。
|
||||
* **如果是便签 (`Notes.TYPE_NOTE`)**:
|
||||
* 隐藏文件夹图标(或显示便签图标)。
|
||||
* 显示修改时间。
|
||||
|
||||
## 4. 补充细节
|
||||
* **系统文件夹**: 像“通话记录”这样的系统文件夹,可以给予特殊的图标。
|
||||
* **数据层支持**: 检查 `NoteInfo` 类是否包含 `notesCount` 字段。如果 `NotesRepository` 的查询逻辑中没有 join 统计数量,可能需要调整 SQL 查询。
|
||||
* *快速检查*: `NotesRepository.queryNotes` 目前没有进行 join 查询,添加实时计数可能影响性能。
|
||||
* *替代方案*: 仅在视觉上区分(图标 + 样式),暂不显示具体数量,或者只显示 "(文件夹)" 后缀。
|
||||
|
||||
## 5. 最终方案
|
||||
为了最小化对数据层的侵入并快速见效:
|
||||
1. **修改 `note_item.xml`**: 增加一个用于显示类型图标的 `ImageView`。
|
||||
2. **修改 `NoteInfoAdapter.java`**:
|
||||
* 如果 `note.type == Notes.TYPE_FOLDER`: 设置图标为文件夹,隐藏时间(或显示为 "文件夹")。
|
||||
* 如果 `note.type == Notes.TYPE_NOTE`: 隐藏图标,显示时间。
|
||||
|
||||
这样可以直观地通过图标区分两者。
|
||||
@ -0,0 +1,61 @@
|
||||
# 置顶功能问题修复与完善计划
|
||||
|
||||
## 1. 问题分析
|
||||
|
||||
根据用户提供的截图和代码分析,目前存在以下问题:
|
||||
|
||||
1. **置顶按钮缺失**: 在多选模式的 Toolbar 中,虽然代码逻辑已经添加,但截图显示只有“删除”和“全选”等按钮,可能因为 `menu_pin` 资源未正确加载或 `ifRoom` 空间不足被折叠到溢出菜单中。
|
||||
2. **置顶状态不可见**: 截图中的笔记列表没有明显的置顶标识(如置顶图标或背景色区分),用户无法直观判断哪些笔记已置顶。
|
||||
3. **排序逻辑确认**: 需要确认置顶操作后,列表是否正确重新排序(置顶项在最前)。
|
||||
|
||||
## 2. 修改计划
|
||||
|
||||
### 2.1 UI 层修复与增强 (`NotesListActivity` & `NoteInfoAdapter`)
|
||||
|
||||
1. **确保置顶按钮可见**:
|
||||
|
||||
* 检查 `note_list_multi_select.xml`,确保 `multi_select_pin` 的 `showAsAction` 属性设置为 `always`,强制显示在 Toolbar 上。
|
||||
|
||||
* 如果空间不足,考虑调整按钮顺序或图标。
|
||||
2. **添加置顶视觉标识**:
|
||||
|
||||
* 修改 `note_item.xml` (需要查看该文件),添加一个用于显示置顶状态的 `ImageView` (例如 `iv_pinned`)。
|
||||
|
||||
* 在 `NoteInfoAdapter.getView` 中,根据 `note.isPinned` 状态控制该图标的可见性。
|
||||
|
||||
* 或者,可以通过改变置顶项的背景色来区分。
|
||||
|
||||
### 2.2 交互逻辑完善 (`NotesListViewModel` & `NotesListActivity`)
|
||||
|
||||
1. **操作反馈**:
|
||||
|
||||
* 在 `NotesListActivity` 中观察 ViewModel 的操作结果,成功置顶/取消置顶后显示 `Toast` 提示(例如“已置顶”、“已取消置顶”)。
|
||||
2. **实时刷新**:
|
||||
|
||||
* 确认 `NotesRepository.batchTogglePin` 完成后是否触发了数据重新加载。目前 ViewModel 中的回调已经调用了 `refreshNotes()`,这部分逻辑应该是正确的。
|
||||
|
||||
### 2.3 数据层确认 (`NotesRepository`)
|
||||
|
||||
1. **排序逻辑**:
|
||||
|
||||
* 再次检查 `queryNotes` 方法中的排序逻辑,确保 `isPinned` 优先级高于 `modifiedDate`。代码中已经实现了 `if (a.isPinned != b.isPinned) return a.isPinned ? -1 : 1;`,逻辑正确。
|
||||
|
||||
## 3. 实施步骤
|
||||
|
||||
1. **修改 Menu 资源**: 将 `note_list_multi_select.xml` 中置顶按钮的 `showAsAction` 改为 `always`。
|
||||
2. **修改 Item 布局**: 在 `note_item.xml` 中添加置顶图标。
|
||||
3. **更新 Adapter**: 在 `NoteInfoAdapter` 中绑定置顶图标数据。
|
||||
4. **添加 Toast 反馈**: 在 `NotesListActivity` 中监听置顶操作完成事件(可能需要 ViewModel 添加一个新的 LiveData 用于通知 UI 操作结果)。
|
||||
|
||||
## 4. 验证方案
|
||||
|
||||
1. 长按进入多选模式,确认右上角出现置顶按钮。
|
||||
2. 选中一个普通笔记,点击置顶,确认:
|
||||
|
||||
* 弹出“已置顶”提示。
|
||||
|
||||
* 笔记移动到列表顶部。
|
||||
|
||||
* 笔记上出现置顶图标。
|
||||
3. 选中一个已置顶笔记,点击取消置顶,确认恢复原位且图标消失。
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
# 解决小米便签编译报错及数据库升级方案
|
||||
|
||||
## 问题分析
|
||||
用户遇到的编译报错 `找不到符号 NoteColumns.TOP` 是因为在 `NoteItemData.java` 中使用了 `NoteColumns.TOP` 常量,但该常量在 `Notes.java` 的 `NoteColumns` 接口中未定义。这通常是因为代码中引入了“置顶”功能的相关逻辑,但缺少了底层数据定义。
|
||||
|
||||
此外,仅修复编译错误是不够的。如果数据库中没有对应的 `top` 列,应用在运行时查询该字段可能会崩溃或行为异常。因此,需要同步更新数据库架构。
|
||||
|
||||
## 解决方案
|
||||
我们将分两步解决此问题:
|
||||
|
||||
### 1. 修复编译错误
|
||||
在 `app/src/main/java/net/micode/notes/data/Notes.java` 文件中,向 `NoteColumns` 接口添加 `TOP` 常量定义。
|
||||
|
||||
### 2. 升级数据库架构
|
||||
在 `app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java` 文件中执行以下操作:
|
||||
1. **升级版本号**:将 `DB_VERSION` 从 `4` 增加到 `5`。
|
||||
2. **更新建表语句**:在 `CREATE_NOTE_TABLE_SQL` 中添加 `top` 字段的定义,确保新安装的应用包含该字段。
|
||||
3. **实现升级逻辑**:添加 `upgradeToV5` 方法,使用 `ALTER TABLE` 语句为旧版本数据库添加 `top` 字段。
|
||||
4. **注册升级路径**:在 `onUpgrade` 方法中调用 `upgradeToV5`。
|
||||
|
||||
## 详细步骤
|
||||
1. **编辑 `Notes.java`**:
|
||||
* 添加 `public static final String TOP = "top";`。
|
||||
2. **编辑 `NotesDatabaseHelper.java`**:
|
||||
* `DB_VERSION` -> `5`。
|
||||
* `CREATE_NOTE_TABLE_SQL`: 添加 `NoteColumns.TOP + " INTEGER NOT NULL DEFAULT 0"`。
|
||||
* 添加 `upgradeToV5(SQLiteDatabase db)` 方法。
|
||||
* 在 `onUpgrade` 中处理 `oldVersion == 4` 的情况。
|
||||
|
||||
通过以上步骤,既能解决当前的编译错误,又能保证应用在部署到设备上后能正常读写新增的“置顶”字段。
|
||||
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>app</name>
|
||||
<comment>Project app created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1768954653382</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
@ -0,0 +1,180 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.tool.SecurityManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PasswordActivity extends Activity {
|
||||
|
||||
public static final String ACTION_SETUP_PASSWORD = "net.micode.notes.action.SETUP_PASSWORD";
|
||||
public static final String ACTION_CHECK_PASSWORD = "net.micode.notes.action.CHECK_PASSWORD";
|
||||
public static final String EXTRA_PASSWORD_TYPE = "extra_password_type";
|
||||
|
||||
private int mMode; // 0: Check, 1: Setup
|
||||
private int mPasswordType;
|
||||
|
||||
private TextView mTvPrompt;
|
||||
private EditText mEtPin;
|
||||
private LockPatternView mLockPatternView;
|
||||
private TextView mTvError;
|
||||
private Button mBtnCancel;
|
||||
|
||||
private String mFirstInput = null; // For setup confirmation
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_password);
|
||||
|
||||
String action = getIntent().getAction();
|
||||
if (ACTION_SETUP_PASSWORD.equals(action)) {
|
||||
mMode = 1;
|
||||
mPasswordType = getIntent().getIntExtra(EXTRA_PASSWORD_TYPE, SecurityManager.TYPE_PIN);
|
||||
} else {
|
||||
mMode = 0;
|
||||
// Check mode: get type from SecurityManager
|
||||
mPasswordType = SecurityManager.getInstance(this).getPasswordType();
|
||||
}
|
||||
|
||||
initViews();
|
||||
setupViews();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
mTvPrompt = findViewById(R.id.tv_prompt);
|
||||
mEtPin = findViewById(R.id.et_pin);
|
||||
mLockPatternView = findViewById(R.id.lock_pattern_view);
|
||||
mTvError = findViewById(R.id.tv_error);
|
||||
mBtnCancel = findViewById(R.id.btn_cancel);
|
||||
|
||||
mBtnCancel.setOnClickListener(v -> {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupViews() {
|
||||
if (mMode == 1) { // Setup
|
||||
mTvPrompt.setText("请设置密码");
|
||||
} else { // Check
|
||||
mTvPrompt.setText("请输入密码");
|
||||
}
|
||||
|
||||
if (mPasswordType == SecurityManager.TYPE_PIN) {
|
||||
mEtPin.setVisibility(View.VISIBLE);
|
||||
mLockPatternView.setVisibility(View.GONE);
|
||||
mEtPin.requestFocus(); // Auto focus
|
||||
setupPinLogic();
|
||||
} else if (mPasswordType == SecurityManager.TYPE_PATTERN) {
|
||||
mEtPin.setVisibility(View.GONE);
|
||||
mLockPatternView.setVisibility(View.VISIBLE);
|
||||
setupPatternLogic();
|
||||
} else {
|
||||
// Should not happen
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPinLogic() {
|
||||
mEtPin.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE ||
|
||||
(event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN)) {
|
||||
handleInput(mEtPin.getText().toString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void setupPatternLogic() {
|
||||
mLockPatternView.setOnPatternListener(new LockPatternView.OnPatternListener() {
|
||||
@Override
|
||||
public void onPatternStart() {
|
||||
mTvError.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPatternCleared() {}
|
||||
|
||||
@Override
|
||||
public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {}
|
||||
|
||||
@Override
|
||||
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
||||
if (pattern.size() < 3) {
|
||||
mTvError.setText("连接至少3个点");
|
||||
mTvError.setVisibility(View.VISIBLE);
|
||||
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
||||
return;
|
||||
}
|
||||
handleInput(LockPatternView.patternToString(pattern));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleInput(String input) {
|
||||
if (TextUtils.isEmpty(input)) return;
|
||||
mTvError.setVisibility(View.INVISIBLE);
|
||||
|
||||
if (mMode == 0) { // Check
|
||||
if (SecurityManager.getInstance(this).checkPassword(input)) {
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
} else {
|
||||
mTvError.setText("密码错误");
|
||||
mTvError.setVisibility(View.VISIBLE);
|
||||
if (mPasswordType == SecurityManager.TYPE_PATTERN) {
|
||||
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
||||
} else {
|
||||
mEtPin.setText("");
|
||||
}
|
||||
}
|
||||
} else { // Setup
|
||||
if (mFirstInput == null) {
|
||||
// First entry
|
||||
mFirstInput = input;
|
||||
mTvPrompt.setText("请再次输入以确认");
|
||||
if (mPasswordType == SecurityManager.TYPE_PATTERN) {
|
||||
mLockPatternView.clearPattern();
|
||||
} else {
|
||||
mEtPin.setText("");
|
||||
}
|
||||
} else {
|
||||
// Second entry
|
||||
if (mFirstInput.equals(input)) {
|
||||
SecurityManager.getInstance(this).setPassword(input, mPasswordType);
|
||||
Toast.makeText(this, "密码设置成功", Toast.LENGTH_SHORT).show();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
} else {
|
||||
mTvError.setText("两次输入不一致,请重试");
|
||||
mTvError.setVisibility(View.VISIBLE);
|
||||
// Reset to start
|
||||
mFirstInput = null;
|
||||
mTvPrompt.setText("请设置密码");
|
||||
if (mPasswordType == SecurityManager.TYPE_PATTERN) {
|
||||
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
||||
mLockPatternView.postDelayed(() -> mLockPatternView.clearPattern(), 1000);
|
||||
} else {
|
||||
mEtPin.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFC107"
|
||||
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,57 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="20dp"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_prompt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="请输入密码"
|
||||
android:textSize="20sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="40dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_pin"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:maxLength="6"
|
||||
android:gravity="center"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<net.micode.notes.ui.LockPatternView
|
||||
android:id="@+id/lock_pattern_view"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/holo_red_dark"
|
||||
android:textSize="14sp"
|
||||
android:visibility="invisible"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="密码错误"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_cancel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/cancel"/>
|
||||
|
||||
</LinearLayout>
|
||||
Loading…
Reference in new issue