commit
a3b5466ecb
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"artifactType": {
|
|
||||||
"type": "APK",
|
|
||||||
"kind": "Directory"
|
|
||||||
},
|
|
||||||
"applicationId": "net.micode.notes",
|
|
||||||
"variantName": "debug",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "SINGLE",
|
|
||||||
"filters": [],
|
|
||||||
"attributes": [],
|
|
||||||
"versionCode": 1,
|
|
||||||
"versionName": "0.1",
|
|
||||||
"outputFile": "app-debug.apk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"elementType": "File"
|
|
||||||
}
|
|
@ -0,0 +1,264 @@
|
|||||||
|
package net.micode.notes.ui;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
|
||||||
|
import net.micode.notes.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LockPatternView extends View {
|
||||||
|
private static final String TAG = "LockPatternView";
|
||||||
|
private Paint pointPaint;
|
||||||
|
private Paint linePaint;
|
||||||
|
private Paint highlightPaint;
|
||||||
|
private int normalColor;
|
||||||
|
private int selectedColor;
|
||||||
|
private int errorColor;
|
||||||
|
private float pointRadius;
|
||||||
|
private float pointInnerRadius;
|
||||||
|
private boolean isError = false;
|
||||||
|
private ValueAnimator pointAnimator;
|
||||||
|
private Point[][] points = new Point[3][3];
|
||||||
|
private List<Point> selectedPoints = new ArrayList<>();
|
||||||
|
private boolean isDrawing = false;
|
||||||
|
private float currentX, currentY;
|
||||||
|
private OnPatternListener patternListener;
|
||||||
|
|
||||||
|
public interface OnPatternListener {
|
||||||
|
void onPatternComplete(String pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockPatternView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setLayerType(LAYER_TYPE_SOFTWARE, null);
|
||||||
|
|
||||||
|
// 获取颜色资源
|
||||||
|
normalColor = getResources().getColor(R.color.gesture_point_normal);
|
||||||
|
selectedColor = getResources().getColor(R.color.gesture_point_selected);
|
||||||
|
errorColor = getResources().getColor(R.color.gesture_point_error);
|
||||||
|
|
||||||
|
// 初始化画笔
|
||||||
|
pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
pointPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
pointPaint.setStrokeWidth(dpToPx(3));
|
||||||
|
|
||||||
|
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
linePaint.setStyle(Paint.Style.STROKE);
|
||||||
|
linePaint.setStrokeWidth(dpToPx(3));
|
||||||
|
linePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
|
||||||
|
highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
highlightPaint.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
// 初始化动画
|
||||||
|
setupAnimator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAnimator() {
|
||||||
|
pointAnimator = ValueAnimator.ofFloat(0f, 1f);
|
||||||
|
pointAnimator.setDuration(300);
|
||||||
|
pointAnimator.setInterpolator(new DecelerateInterpolator());
|
||||||
|
pointAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
float value = (float) animation.getAnimatedValue();
|
||||||
|
pointInnerRadius = pointRadius * 0.3f + (pointRadius * 0.7f * value);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private float dpToPx(float dp) {
|
||||||
|
return dp * getContext().getResources().getDisplayMetrics().density;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
float size = Math.min(w, h);
|
||||||
|
pointRadius = size / 12; // 点的大小
|
||||||
|
pointInnerRadius = pointRadius * 0.3f;
|
||||||
|
|
||||||
|
// 修正计算逻辑
|
||||||
|
float padding = size / 6; // 边距
|
||||||
|
float cellWidth = (size - (padding * 2)) / 2; // 这里导致了问题
|
||||||
|
|
||||||
|
// 修改为3x3网格的计算方式
|
||||||
|
float cellSize = (size - (padding * 2)) / 3; // 每个格子的大小
|
||||||
|
|
||||||
|
// 计算9个点的位置
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
float x = padding + j * cellSize + (cellSize / 2); // 加上半个格子大小使点居中
|
||||||
|
float y = padding + i * cellSize + (cellSize / 2);
|
||||||
|
points[i][j] = new Point(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
// 绘制连线
|
||||||
|
if (selectedPoints.size() > 0) {
|
||||||
|
linePaint.setColor(isError ? errorColor : selectedColor);
|
||||||
|
Path path = new Path();
|
||||||
|
Point first = selectedPoints.get(0);
|
||||||
|
path.moveTo(first.x, first.y);
|
||||||
|
|
||||||
|
for (int i = 1; i < selectedPoints.size(); i++) {
|
||||||
|
Point point = selectedPoints.get(i);
|
||||||
|
path.lineTo(point.x, point.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDrawing) {
|
||||||
|
path.lineTo(currentX, currentY);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPath(path, linePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制所有点
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
Point point = points[i][j];
|
||||||
|
boolean isSelected = selectedPoints.contains(point);
|
||||||
|
|
||||||
|
// 绘制外圈
|
||||||
|
pointPaint.setColor(isSelected ?
|
||||||
|
(isError ? errorColor : selectedColor) : normalColor);
|
||||||
|
canvas.drawCircle(point.x, point.y, pointRadius, pointPaint);
|
||||||
|
|
||||||
|
// 绘制内圈
|
||||||
|
if (isSelected) {
|
||||||
|
highlightPaint.setColor(isSelected ?
|
||||||
|
(isError ? errorColor : selectedColor) : normalColor);
|
||||||
|
canvas.drawCircle(point.x, point.y, pointInnerRadius, highlightPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentX = event.getX();
|
||||||
|
currentY = event.getY();
|
||||||
|
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
isDrawing = true;
|
||||||
|
selectedPoints.clear();
|
||||||
|
checkPoint(currentX, currentY);
|
||||||
|
invalidate();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
if (isDrawing) {
|
||||||
|
checkPoint(currentX, currentY);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
if (isDrawing) {
|
||||||
|
isDrawing = false;
|
||||||
|
if (patternListener != null && selectedPoints.size() > 0) {
|
||||||
|
patternListener.onPatternComplete(getPatternString());
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPoint(float x, float y) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
Point point = points[i][j];
|
||||||
|
float dx = x - point.x;
|
||||||
|
float dy = y - point.y;
|
||||||
|
float distance = (float) Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < pointRadius && !selectedPoints.contains(point)) {
|
||||||
|
selectedPoints.add(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPatternString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Point point : selectedPoints) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
if (points[i][j] == point) {
|
||||||
|
sb.append(i * 3 + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showError() {
|
||||||
|
isError = true;
|
||||||
|
pointAnimator.start();
|
||||||
|
postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
isError = false;
|
||||||
|
clearPattern();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
public void showSuccess() {
|
||||||
|
selectedColor = getResources().getColor(R.color.gesture_success_color);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clearPattern() {
|
||||||
|
isError = false;
|
||||||
|
selectedPoints.clear();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPatternListener(OnPatternListener listener) {
|
||||||
|
this.patternListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Point {
|
||||||
|
float x, y;
|
||||||
|
Point(float x, float y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
|
int width = getMeasuredWidth();
|
||||||
|
// 确保是正方形
|
||||||
|
setMeasuredDimension(width, width);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package net.micode.notes.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.micode.notes.R;
|
||||||
|
import net.micode.notes.data.Notes;
|
||||||
|
import net.micode.notes.data.Notes.NoteColumns;
|
||||||
|
|
||||||
|
public class NoteGesturePasswordActivity extends Activity {
|
||||||
|
private LockPatternView lockPatternView;
|
||||||
|
private TextView gestureStatus;
|
||||||
|
private Button btnConfirm;
|
||||||
|
private String tempPattern;
|
||||||
|
private long noteId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.note_gesture_password);
|
||||||
|
|
||||||
|
noteId = getIntent().getLongExtra("note_id", -1);
|
||||||
|
if (noteId == -1) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
lockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view);
|
||||||
|
gestureStatus = (TextView) findViewById(R.id.gesture_status);
|
||||||
|
btnConfirm = (Button) findViewById(R.id.btn_confirm);
|
||||||
|
btnConfirm.setEnabled(false);
|
||||||
|
|
||||||
|
// 修改这部分代码,使用新的接口
|
||||||
|
lockPatternView.setPatternListener(new LockPatternView.OnPatternListener() {
|
||||||
|
@Override
|
||||||
|
public void onPatternComplete(String pattern) {
|
||||||
|
if (pattern.length() < 4) {
|
||||||
|
gestureStatus.setText(R.string.gesture_too_short);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempPattern == null) {
|
||||||
|
tempPattern = pattern;
|
||||||
|
gestureStatus.setText(R.string.confirm_gesture_pattern);
|
||||||
|
btnConfirm.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
if (tempPattern.equals(pattern)) {
|
||||||
|
btnConfirm.setEnabled(true);
|
||||||
|
gestureStatus.setText(R.string.gesture_matched);
|
||||||
|
} else {
|
||||||
|
tempPattern = null;
|
||||||
|
gestureStatus.setText(R.string.gesture_not_matched);
|
||||||
|
btnConfirm.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btnConfirm.setOnClickListener(v -> {
|
||||||
|
if (tempPattern != null) {
|
||||||
|
saveGesturePassword(tempPattern);
|
||||||
|
Toast.makeText(this, R.string.gesture_password_set, Toast.LENGTH_SHORT).show();
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveGesturePassword(String pattern) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(NoteColumns.GESTURE_PASSWORD, pattern);
|
||||||
|
getContentResolver().update(
|
||||||
|
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
|
||||||
|
values, null, null);
|
||||||
|
|
||||||
|
// 添加返回密码到结果Intent
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra("pattern", pattern);
|
||||||
|
setResult(RESULT_OK, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
package net.micode.notes.ui;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.micode.notes.R;
|
||||||
|
import net.micode.notes.data.Notes;
|
||||||
|
|
||||||
|
public class NoteGestureVerifyActivity extends Activity {
|
||||||
|
private static final String TAG = "NoteGestureVerifyActivity";
|
||||||
|
private static final int MAX_TRIES = 5;
|
||||||
|
|
||||||
|
private LockPatternView lockPatternView;
|
||||||
|
private TextView gestureStatus;
|
||||||
|
private ImageView lockIcon;
|
||||||
|
private TextView forgotPattern;
|
||||||
|
private int currentTries = 0;
|
||||||
|
private String savedPattern;
|
||||||
|
private long noteId;
|
||||||
|
private Handler handler = new Handler();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// 设置全屏显示
|
||||||
|
getWindow().setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
setContentView(R.layout.note_gesture_verify);
|
||||||
|
|
||||||
|
initViews();
|
||||||
|
setupListeners();
|
||||||
|
loadSavedPattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
lockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view);
|
||||||
|
gestureStatus = (TextView) findViewById(R.id.gesture_status);
|
||||||
|
lockIcon = (ImageView) findViewById(R.id.iv_lock_icon);
|
||||||
|
forgotPattern = (TextView) findViewById(R.id.tv_forgot_pattern);
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
updateStatusText(getString(R.string.gesture_verify_hint));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupListeners() {
|
||||||
|
lockPatternView.setPatternListener(new LockPatternView.OnPatternListener() {
|
||||||
|
@Override
|
||||||
|
public void onPatternComplete(String pattern) {
|
||||||
|
verifyPattern(pattern);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
forgotPattern.setOnClickListener(v -> handleForgotPattern());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("LongLogTag")
|
||||||
|
private void loadSavedPattern() {
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
noteId = getIntent().getLongExtra("note_id", -1);
|
||||||
|
if (noteId == -1) {
|
||||||
|
Log.e(TAG, "Invalid note ID");
|
||||||
|
finishWithError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库读取手势密码
|
||||||
|
cursor = getContentResolver().query(
|
||||||
|
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
|
||||||
|
new String[]{Notes.NoteColumns.GESTURE_PASSWORD},
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
savedPattern = cursor.getString(0);
|
||||||
|
if (savedPattern == null || savedPattern.isEmpty()) {
|
||||||
|
Log.e(TAG, "No pattern set for this note");
|
||||||
|
finishWithSuccess(); // 如果没有设置密码,直接通过验证
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Note not found");
|
||||||
|
finishWithError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error loading saved pattern", e);
|
||||||
|
finishWithError();
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyPattern(String pattern) {
|
||||||
|
if (pattern.equals(savedPattern)) {
|
||||||
|
showSuccessState();
|
||||||
|
handler.postDelayed(this::finishWithSuccess, 500);
|
||||||
|
} else {
|
||||||
|
showErrorState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSuccessState() {
|
||||||
|
gestureStatus.setTextColor(getResources().getColor(R.color.gesture_success_color));
|
||||||
|
gestureStatus.setText(R.string.gesture_verify_success);
|
||||||
|
lockIcon.setImageTintList(ColorStateList.valueOf(
|
||||||
|
getResources().getColor(R.color.gesture_success_color)));
|
||||||
|
|
||||||
|
lockPatternView.showSuccess();
|
||||||
|
|
||||||
|
// 添加成功动画效果
|
||||||
|
lockIcon.animate()
|
||||||
|
.scaleX(1.2f)
|
||||||
|
.scaleY(1.2f)
|
||||||
|
.setDuration(200)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
lockIcon.animate()
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.setDuration(100)
|
||||||
|
.start();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorState() {
|
||||||
|
currentTries++;
|
||||||
|
if (currentTries >= MAX_TRIES) {
|
||||||
|
finishWithError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gestureStatus.setTextColor(getResources().getColor(R.color.gesture_point_error));
|
||||||
|
gestureStatus.setText(getString(R.string.gesture_verify_error, MAX_TRIES - currentTries));
|
||||||
|
lockIcon.setImageTintList(ColorStateList.valueOf(
|
||||||
|
getResources().getColor(R.color.gesture_point_error)));
|
||||||
|
|
||||||
|
lockPatternView.showError();
|
||||||
|
|
||||||
|
// 添加错误动画效果
|
||||||
|
lockIcon.animate()
|
||||||
|
.translationX(20f)
|
||||||
|
.setDuration(50)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
lockIcon.animate()
|
||||||
|
.translationX(-20f)
|
||||||
|
.setDuration(100)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
lockIcon.animate()
|
||||||
|
.translationX(0f)
|
||||||
|
.setDuration(50)
|
||||||
|
.start();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
handler.postDelayed(() -> {
|
||||||
|
if (!isFinishing()) {
|
||||||
|
gestureStatus.setTextColor(getResources().getColor(R.color.gesture_text_color));
|
||||||
|
gestureStatus.setText(R.string.gesture_verify_hint);
|
||||||
|
lockIcon.setImageTintList(ColorStateList.valueOf(
|
||||||
|
getResources().getColor(R.color.gesture_icon_color)));
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleForgotPattern() {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.gesture_forgot_title)
|
||||||
|
.setMessage(R.string.gesture_forgot_message)
|
||||||
|
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||||
|
// 这里可以添加重置密码的逻辑
|
||||||
|
Toast.makeText(this, "请联系管理员重置密码", Toast.LENGTH_SHORT).show();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, null);
|
||||||
|
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishWithSuccess() {
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishWithError() {
|
||||||
|
Toast.makeText(this, "验证失败次数过多,请稍后重试", Toast.LENGTH_SHORT).show();
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatusText(String text) {
|
||||||
|
if (gestureStatus != null) {
|
||||||
|
gestureStatus.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
// 返回时取消验证
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (handler != null) {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
<solid android:color="@color/gesture_panel_background" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/gesture_panel_border" />
|
||||||
|
</shape>
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/gesture_button_ripple">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="24dp" />
|
||||||
|
<solid android:color="@color/gesture_button_background" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 阴影层 -->
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#20000000" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<!-- 主背景层 -->
|
||||||
|
<item android:top="1dp" android:left="1dp" android:right="1dp" android:bottom="2dp">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/gesture_panel_background" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/gesture_icon_color"
|
||||||
|
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2L8.9,8L8.9,6zM18,20L6,20L6,10h12v10z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,42 @@
|
|||||||
|
<?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:background="@color/gesture_background"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gesture_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:text="@string/gesture_instruction"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="@color/gesture_text_color"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<net.micode.notes.ui.LockPatternView
|
||||||
|
android:id="@+id/lock_pattern_view"
|
||||||
|
android:layout_width="300dp"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/gesture_background" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_confirm"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/confirm_gesture"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:background="@drawable/gesture_button_background"
|
||||||
|
android:textColor="@color/gesture_button_text" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -0,0 +1,67 @@
|
|||||||
|
<?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:fitsSystemWindows="true"
|
||||||
|
android:background="@color/gesture_background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<!-- 顶部图标 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_lock_icon"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:src="@drawable/ic_lock_outline"
|
||||||
|
android:tint="@color/gesture_icon_color" />
|
||||||
|
|
||||||
|
<!-- 状态文本 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gesture_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/gesture_verify_hint"
|
||||||
|
android:textColor="@color/gesture_text_color"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<!-- 九宫格容器 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="32dp"
|
||||||
|
android:layout_marginRight="32dp"
|
||||||
|
android:background="@drawable/gesture_panel_background">
|
||||||
|
|
||||||
|
<net.micode.notes.ui.LockPatternView
|
||||||
|
android:id="@+id/lock_pattern_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="24dp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 底部提示文本 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_forgot_pattern"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/gesture_forgot_pattern"
|
||||||
|
android:textColor="@color/gesture_link_color"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in new issue