diff --git a/winboll/build.properties b/winboll/build.properties index ff0f384..224209f 100644 --- a/winboll/build.properties +++ b/winboll/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Apr 30 12:07:31 HKT 2026 +#Thu Apr 30 14:58:25 CST 2026 stageCount=27 libraryProject= baseVersion=15.11 publishVersion=15.11.26 -buildCount=0 +buildCount=11 baseBetaVersion=15.11.27 diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml index bd4f999..2f5cc47 100644 --- a/winboll/src/main/AndroidManifest.xml +++ b/winboll/src/main/AndroidManifest.xml @@ -293,6 +293,19 @@ + + + + + + + + + + + + finish()); + + mContainer = findViewById(R.id.container); + mPatternView = new PatternView(this); + mContainer.addView(mPatternView); + mPatternView.invalidate(); + + mNeedRestart = false; + boolean isEnoughPoints = savedInstanceIsEnoughPoints(); + + if (savedInstanceState != null) { + mIsInErrorState = savedInstanceState.getBoolean(KEY_ERROR_STATE, false); + mNeedRestart = savedInstanceState.getBoolean(KEY_ERROR_REPEAT_PATTERN, false); + } + + if (mIsInErrorState) { + mPatternView.invalidate(); + } + } + + boolean savedInstanceIsEnoughPoints() { + int count = 0; + if (mPatternView != null) { + for (int i = 0; i < 9; i++) { + if (mPatternView.mDotState[i] == 1) { + count++; + } + } + } + return count >= 4 || count == 0; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + if (mIsInErrorState) { + outState.putBoolean(KEY_ERROR_STATE, mIsInErrorState); + } + if (mNeedRestart) { + outState.putBoolean(KEY_ERROR_REPEAT_PATTERN, mNeedRestart); + } + super.onSaveInstanceState(outState); + } + + private void showErrorState() { + mIsInErrorState = true; + invalidatePattern(); + mHandler.postDelayed(() -> { + mIsInErrorState = false; + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply(); + invalidatePattern(); + if (mPatternView != null) mPatternView.invalidate(); + }, PATTERN_ERROR_DURATION); + } + + private void clearErrorState() { + mIsInErrorState = false; + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply(); + invalidatePattern(); + if (mPatternView != null) mPatternView.invalidate(); + } + + private void showErrorToast() { + android.widget.Toast.makeText(this, "图案点数不足,请重新绘制", + android.widget.Toast.LENGTH_SHORT).show(); + mNeedRestart = true; + } + + private void showSuccessDialog() { + android.app.AlertDialog alertDialog = new android.app.AlertDialog.Builder(this) + .setTitle("设置成功") + .setMessage("图案密码已设置成功") + .setPositiveButton("确定", (dialog, which) -> finish()) + .setCancelable(false) + .create(); + alertDialog.show(); + } + + void finishWithRestart() { + finish(); + } + + private void invalidatePattern() { + if (mPatternView != null) { + mPatternView.invalidate(); + } + } + + class PatternView extends FrameLayout { + int mPatternSize = 0; + int MAX_DOT_COUNT = 9; + int[] mDotX = new int[MAX_DOT_COUNT]; + int[] mDotY = new int[MAX_DOT_COUNT]; + int[] mDotState = new int[MAX_DOT_COUNT]; + Bitmap mDotBitmap; + Paint mPaintConnector; + Paint mPaintErrorBackground; + int mDotCount = 0; + + PatternView(Context context) { + super(context); + setBackgroundColor(Color.WHITE); + for (int i = 0; i < MAX_DOT_COUNT; i++) { + mDotX[i] = -1; + mDotY[i] = -1; + mDotState[i] = 0; + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (w == 0 || h == 0) return; + mPatternSize = w > h ? h : w; + int grid = 3; + int cell = mPatternSize / grid; + + for (int i = 0; i < MAX_DOT_COUNT; i++) { + mDotX[i] = (i % grid) * cell + cell / 2 - cell / 24; + mDotY[i] = (i / grid) * cell + cell / 2 - cell / 24; + mDotState[i] = 0; + } + + if (mDotBitmap == null) { + mDotBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dot_darkgreen_dark); + } + if (mPaintConnector == null) { + mPaintConnector = new Paint(Paint.FILTER_BITMAP_FLAG); + mPaintConnector.setColor(-0xFF006400); + } + if (mPaintErrorBackground == null) { + mPaintErrorBackground = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintErrorBackground.setColor(Color.RED); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mDotCount > 0) return false; + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + invalidate(); + return true; + case MotionEvent.ACTION_MOVE: + float x = event.getX(); + float y = event.getY(); + + for (int i = 0; i < MAX_DOT_COUNT; i++) { + int dx = (int) Math.abs(x - mDotX[i]); + int dy = (int) Math.abs(y - mDotY[i]); + if (dx <= DOT_RADIUS && dy <= DOT_RADIUS && mDotState[i] == 0) { + mDotState[i] = 1; + mDotCount++; + } + } + + for (int i = 0; i < mDotCount - 1; i++) { + int a = -1, b = -1; + for (int k = 0; k < MAX_DOT_COUNT; k++) { + if (mDotState[k] == 1) { + if (a < 0) a = k; + else b = k; + } + } + if (a >= 0 && b >= 0) { + a = Math.min(a, b); + b = Math.max(a, b); + } + if (mDotState[a] == 1 && mDotState[b] == 1) { + int dx = mDotX[b] - mDotX[a]; + int dy = mDotY[b] - mDotY[a]; + if ((Math.abs(dx) <= 1 && Math.abs(dy) <= 1) || + (Math.abs(dx) <= 2 && Math.abs(dy) <= 1)) { + if (mDotState[b] == 1) { + for (int k = a + 1; k < b; k++) { + if (mDotState[k] == 0) { + mDotState[k] = 1; + } + } + mDotCount += (b - a - 1); + } + } + } + } + invalidate(); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mDotCount < 4) { + showErrorState(); + showErrorToast(); + } + break; + } + return true; + } + + @Override + public void onDraw(Canvas canvas) { + if (mPatternSize == 0) return; + + int activeCount = 0; + for (int i = 0; i < MAX_DOT_COUNT; i++) { + if (mDotState[i] == 1) activeCount++; + } + + if (activeCount == 0) { + mPaintErrorBackground.setAlpha(50); + } else { + mPaintErrorBackground.setAlpha(mIsInErrorState ? 80 : 60); + } + + canvas.clipRect(0, 0, mPatternSize * 80 / 100, mPatternSize * 80 / 100); + + canvas.drawRect(0, 0, mPatternSize, mPatternSize, mPaintErrorBackground); + + if (mDotBitmap != null) { + for (int i = 0; i < MAX_DOT_COUNT; i++) { + if (mDotState[i] == 1) { + canvas.drawBitmap(mDotBitmap, mDotX[i], mDotY[i], mPaintConnector); + } + } + } + } + } +} diff --git a/winboll/src/main/res/drawable/dot_background.xml b/winboll/src/main/res/drawable/dot_background.xml new file mode 100644 index 0000000..f9fc6fc --- /dev/null +++ b/winboll/src/main/res/drawable/dot_background.xml @@ -0,0 +1,8 @@ + + + + diff --git a/winboll/src/main/res/drawable/dot_darkgreen_dark.xml b/winboll/src/main/res/drawable/dot_darkgreen_dark.xml new file mode 100644 index 0000000..25f65b4 --- /dev/null +++ b/winboll/src/main/res/drawable/dot_darkgreen_dark.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/winboll/src/main/res/layout/activity_pattern_lock.xml b/winboll/src/main/res/layout/activity_pattern_lock.xml new file mode 100644 index 0000000..2298d46 --- /dev/null +++ b/winboll/src/main/res/layout/activity_pattern_lock.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/winboll/src/main/res/values/colors.xml b/winboll/src/main/res/values/colors.xml index 479769a..b49a043 100644 --- a/winboll/src/main/res/values/colors.xml +++ b/winboll/src/main/res/values/colors.xml @@ -3,4 +3,5 @@ #009688 #00796B #FF9800 + #000000 \ No newline at end of file diff --git a/winboll/src/main/res/values/strings.xml b/winboll/src/main/res/values/strings.xml index ae4a142..0cc6cdd 100644 --- a/winboll/src/main/res/values/strings.xml +++ b/winboll/src/main/res/values/strings.xml @@ -12,4 +12,5 @@ WinBoLL WinBoLL APP MyTermuxActivity + 图案密码设置