Skip to content

Commit

Permalink
✨ localauth
Browse files Browse the repository at this point in the history
  • Loading branch information
niuhuan committed May 11, 2024
1 parent 3aab1c2 commit ba40bd6
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 7 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />

<queries>
<intent>
Expand Down
73 changes: 67 additions & 6 deletions android/app/src/main/kotlin/opensource/jasmine/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package opensource.jasmine


import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.hardware.biometrics.BiometricPrompt
import android.os.*
import android.provider.MediaStore
import android.util.Log
import android.view.Display
import android.view.KeyEvent
import android.view.WindowManager
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.Executors
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.sync.Mutex
import mobile.Mobile
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.file.Files
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import opensource.jenny.Jni

class MainActivity : FlutterActivity() {
Expand Down Expand Up @@ -58,6 +70,7 @@ class MainActivity : FlutterActivity() {
call.arguments<String>() ?: throw Exception("need arg"),
)
"picturesDir" -> picturesDir().absolutePath
"verifyAuthentication" -> auth()
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -228,4 +241,52 @@ class MainActivity : FlutterActivity() {
}
}

// withCoroutine -> queue
private fun auth(): Boolean {
var queue = LinkedBlockingQueue<Boolean>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
var mBiometricPrompt = BiometricPrompt.Builder(this)
.setTitle("验证身份")
.setDescription("需要验证您的身份")
.setNegativeButton(
"取消", mainExecutor
) { _, _ -> queue.add(false) }
.build()


var mCancellationSignal = CancellationSignal()
mCancellationSignal.setOnCancelListener {
queue.add(false)
}

var mAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
queue.add(false)
}

override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
queue.add(false)
}

override fun onAuthenticationSucceeded(result1: BiometricPrompt.AuthenticationResult?) {
super.onAuthenticationSucceeded(result1)
queue.add(true)
}
}

mBiometricPrompt.authenticate(
mCancellationSignal,
mainExecutor,
mAuthenticationCallback
)

} else {
queue.add(false)
}

return queue.poll(5, TimeUnit.MINUTES) ?: false
}

}
1 change: 1 addition & 0 deletions ci/version.info.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
v1.6.7

- [x] ✨ 对分类进行排序(设置项)
- [x] ✨ 生物验证
11 changes: 11 additions & 0 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit
import Flutter
import LocalAuthentication

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
Expand Down Expand Up @@ -50,6 +51,16 @@ import Flutter
}
case "iosGetDocumentDir" :
result(documentDirectory)
case "verifyAuthentication":
let context = LAContext()
let can = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
guard can == true else {
result(false)
return
}
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "身份验证") { (success, error) in
result(success)
}
default:
result(FlutterMethodNotImplemented)
}
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSFaceIDUsageDescription</key>
<string>Authenticating using face id</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
Expand Down
4 changes: 4 additions & 0 deletions lib/basic/methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,10 @@ class Methods {
Future setProServerName(String serverName) async {
return await _invoke("set_pro_server_name", serverName);
}

Future<bool> verifyAuthentication() async {
return await _channel.invokeMethod("verifyAuthentication");
}
}

class _Response {
Expand Down
80 changes: 80 additions & 0 deletions lib/configs/Authentication.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'dart:io';

import 'package:flutter/material.dart';

import '../basic/commons.dart';
import '../basic/methods.dart';
import 'DesktopAuthenticationScreen.dart';
import 'android_version.dart';
const _propertyName = "authentication";
late bool _authentication;

Future<void> initAuthentication() async {
if (Platform.isIOS || androidVersion >= 29) {
_authentication =
(await methods.loadProperty(_propertyName)) == "true";
}
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
_authentication = await needDesktopAuthentication();
}
}

bool currentAuthentication() {
return _authentication;
}

Future<bool> verifyAuthentication(BuildContext context) async {
if (Platform.isIOS || androidVersion >= 29) {
return await methods.verifyAuthentication();
}
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
return await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const VerifyPassword())) ==
true;
}
return false;
}

Widget authenticationSetting() {
if (Platform.isIOS || androidVersion >= 29) {
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return ListTile(
title: const Text("进入APP时验证身份(如果系统已经录入密码或指纹)"),
subtitle: Text(_authentication ? "是" : "否"),
onTap: () async {
await _chooseAuthentication(context);
setState(() {});
},
);
},
);
}
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
return StatefulBuilder(builder: (
BuildContext context,
void Function(void Function()) setState,
) {
return ListTile(
title: const Text("设置应用程序密码"),
onTap: () async {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const SetPassword()));
},
);
});
}
return Container();
}

Future<void> _chooseAuthentication(BuildContext context) async {
if (await methods.verifyAuthentication()) {
String? result =
await chooseListDialog<String>(context, title: "进入APP时验证身份", values: ["是", "否"]);
if (result != null) {
var target = result == "是";
await methods.saveProperty(_propertyName, "$target");
_authentication = target;
}
}
}
134 changes: 134 additions & 0 deletions lib/configs/DesktopAuthenticationScreen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:jasmine/basic/methods.dart';

const _key = "desktopAuthPassword";

Future<bool> needDesktopAuthentication() async {
return await methods.loadProperty(_key) != "";
}

class VerifyPassword extends StatefulWidget {
const VerifyPassword({Key? key}) : super(key: key);

@override
State<StatefulWidget> createState() => _VerifyPasswordState();
}

class _VerifyPasswordState extends State<VerifyPassword> {
String _password = "";

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(
children: [
Expanded(child: Container()),
TextField(
decoration: const InputDecoration(labelText: "当前密码"),
onChanged: (value) {
_password = value;
},
),
Container(height: 10),
ElevatedButton(
onPressed: () async {
String savedPassword = await methods.loadProperty(_key);
if (_password == savedPassword) {
Navigator.of(context).pop(true);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text("密码错误")));
}
},
child: const Text("确定"),
),
Expanded(child: Container()),
],
),
),
),
);
}
}

class SetPassword extends StatefulWidget {
const SetPassword({Key? key}) : super(key: key);

@override
State<StatefulWidget> createState() => _SetPasswordState();
}

class _SetPasswordState extends State<SetPassword> {
String _password = "";
String _password2 = "";

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(
children: [
const Text(
"密码初始化",
style: TextStyle(
height: 18,
),
),
Container(
height: 10,
),
TextField(
decoration: const InputDecoration(labelText: "密码"),
onChanged: (value) {
_password = value;
},
),
Container(
height: 10,
),
TextField(
decoration: const InputDecoration(labelText: "再次输入密码"),
onChanged: (value) {
_password2 = value;
},
),
Container(
height: 10,
),
Row(
children: [
ElevatedButton(
onPressed: () async {
Navigator.of(context).pop(false);
},
child: const Text("取消"),
),
Container(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () async {
if (_password != _password2) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("两次输入的密码不一致")));
return;
}
await methods.saveProperty(_key, _password);
Navigator.of(context).pop(true);
},
child: const Text("设置密码"),
),
),
],
),
],
),
),
),
);
}
}
2 changes: 2 additions & 0 deletions lib/configs/configs.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:jasmine/configs/Authentication.dart';
import 'package:jasmine/configs/android_display_mode.dart';
import 'package:jasmine/configs/android_version.dart';
import 'package:jasmine/configs/display_jmcode.dart';
Expand Down Expand Up @@ -68,4 +69,5 @@ Future initConfigs() async {
await initDisplayJmcode();
await initSearchTitleWords();
await initCategoriesSort();
await initAuthentication();
}
Loading

0 comments on commit ba40bd6

Please sign in to comment.