optimize UI and simplify code of Authenticaion

This commit is contained in:
BinTianqi
2024-05-16 21:17:53 +08:00
parent fcfaedd257
commit a2082641b1
9 changed files with 144 additions and 75 deletions

View File

@@ -21,8 +21,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 21
targetSdk = 34
versionCode = 27
versionName = "5.2"
versionCode = 28
versionName = "5.3"
multiDexEnabled = false
}

View File

@@ -3,14 +3,15 @@ package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.widget.FrameLayout
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.biometric.BiometricPrompt.PromptInfo.Builder
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -18,35 +19,38 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlinx.coroutines.*
class AuthFragment: Fragment() {
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("UnrememberedMutableState")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val sharedPref = context?.getSharedPreferences("data", Context.MODE_PRIVATE)!!
val context = requireContext()
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)!!
val canStartAuth = mutableStateOf(true)
val onAuthSucceed = {
val fragmentManager = this.parentFragmentManager
val fragment = fragmentManager.findFragmentByTag("auth")
if(fragment != null) {
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.base, homeFragment)
fragmentTransaction.commit()
val transaction = fragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
transaction.add(R.id.base, homeFragment)
requireActivity().findViewById<FrameLayout>(R.id.base).bringChildToFront(homeFragment.view)
transaction.commit()
lifecycleScope.launch {
delay(500)
fragmentManager.beginTransaction().remove(this@AuthFragment).commit()
}
}
val promptInfo = PromptInfo.Builder()
.setTitle("Auth")
.setSubtitle("Auth OwnDroid with password or biometric")
val promptInfo = Builder()
.setTitle(context.getText(R.string.authenticate))
.setSubtitle(context.getText(R.string.auth_with_bio))
.setConfirmationRequired(true)
var fallback = false
val callback = object: AuthenticationCallback() {
@@ -56,22 +60,26 @@ class AuthFragment: Fragment() {
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
if(errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) onAuthSucceed()
if(errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) fallback = true
if(errorCode == BiometricPrompt.ERROR_CANCELED) return
Toast.makeText(context, errString, Toast.LENGTH_SHORT).show()
when(errorCode){
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
else -> canStartAuth.value = true
}
Log.e("OwnDroid", errString.toString())
}
}
GlobalScope.launch(Dispatchers.Main) {
lifecycleScope.launch(Dispatchers.Main) {
while(true){
if(fallback){
val fallbackPromptInfo = PromptInfo.Builder()
val fallbackPromptInfo = Builder()
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setTitle("Auth")
.setSubtitle("Auth OwnDroid with password")
.setTitle(context.getText(R.string.authenticate))
.setSubtitle(context.getText(R.string.auth_with_password))
.setConfirmationRequired(true)
.build()
authWithBiometricPrompt(requireActivity(), fallbackPromptInfo, callback)
val executor = ContextCompat.getMainExecutor(requireContext())
val biometricPrompt = BiometricPrompt(requireActivity(), executor, callback)
biometricPrompt.authenticate(fallbackPromptInfo)
break
}
delay(50)
@@ -82,7 +90,7 @@ class AuthFragment: Fragment() {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
OwnDroidTheme(materialYou.value, blackTheme.value) {
Auth(this@AuthFragment.requireActivity(), callback, promptInfo)
Auth(this@AuthFragment, promptInfo, callback, canStartAuth)
}
}
}
@@ -90,43 +98,57 @@ class AuthFragment: Fragment() {
}
@Composable
fun Auth(activity: FragmentActivity, callback: AuthenticationCallback, promptInfo: PromptInfo.Builder) {
val context = LocalContext.current
fun Auth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback, canStartAuth: MutableState<Boolean>) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
){
Text(text = "Authenticate", style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground)
Text(
text = stringResource(R.string.authenticate),
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.onBackground
)
LaunchedEffect(Unit){
startAuth(activity, promptInfo, callback)
canStartAuth.value = false
}
Button(
onClick = {
val bioManager = BiometricManager.from(context)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPref.getBoolean("bio_auth", false)){
when(BiometricManager.BIOMETRIC_SUCCESS){
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setNegativeButtonText("Use password")
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setNegativeButtonText("Use password")
else -> promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
}
}else{
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
}
authWithBiometricPrompt(activity, promptInfo.build(), callback)
}
startAuth(activity, promptInfo, callback)
canStartAuth.value = false
},
enabled = canStartAuth.value
){
Text(text = "Start")
Text(text = stringResource(R.string.start))
}
}
}
private fun authWithBiometricPrompt(activity: FragmentActivity, promptInfo: PromptInfo, callback: AuthenticationCallback) {
val executor = ContextCompat.getMainExecutor(activity.applicationContext)
private fun startAuth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback){
val context = activity.requireContext()
val bioManager = BiometricManager.from(context)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPref.getBoolean("bio_auth", false)){
when(BiometricManager.BIOMETRIC_SUCCESS){
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setNegativeButtonText(context.getText(R.string.use_password))
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setNegativeButtonText(context.getText(R.string.use_password))
else -> promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
}
}else{
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
}
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt = BiometricPrompt(activity, executor, callback)
biometricPrompt.authenticate(promptInfo)
biometricPrompt.authenticate(promptInfo.build())
}

View File

@@ -5,10 +5,7 @@ import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme.typography
@@ -24,10 +21,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.ui.*
@Composable
fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, blackTheme: MutableState<Boolean>){
@@ -65,8 +59,8 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
@Composable
private fun Home(navCtrl: NavHostController){
Column(modifier = Modifier.fillMaxSize()){
SubPageItem(R.string.setting,"",R.drawable.settings_fill0){navCtrl.navigate("Theme")}
SubPageItem(R.string.security,"",R.drawable.settings_fill0){navCtrl.navigate("Auth")}
SubPageItem(R.string.theme,"",R.drawable.format_paint_fill0){navCtrl.navigate("Theme")}
SubPageItem(R.string.security,"",R.drawable.lock_fill0){navCtrl.navigate("Auth")}
SubPageItem(R.string.about,"",R.drawable.info_fill0){navCtrl.navigate("About")}
}
}
@@ -103,16 +97,14 @@ private fun AuthSettings(){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
if(VERSION.SDK_INT>=30){
SwitchItem(
R.string.lock_owndroid, "", null,
{ auth },
{
sharedPref.edit().putBoolean("auth",it).apply()
auth = sharedPref.getBoolean("auth",false)
}
)
}
SwitchItem(
R.string.lock_owndroid, "", null,
{ auth },
{
sharedPref.edit().putBoolean("auth",it).apply()
auth = sharedPref.getBoolean("auth",false)
}
)
if(auth){
SwitchItem(
R.string.enable_bio_auth, "", null,
@@ -120,6 +112,11 @@ private fun AuthSettings(){
{ sharedPref.edit().putBoolean("bio_auth",it).apply() }
)
}
Box(modifier = Modifier.padding(horizontal = 8.dp)){
Information {
Text(text = stringResource(R.string.auth_on_start))
}
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="0.9"
android:toXScale="1.0"
android:fromYScale="0.9"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="250" />
<alpha
android:duration="200"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="200"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.5" />
</set>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M440,880q-33,0 -56.5,-23.5T360,800v-160L240,640q-33,0 -56.5,-23.5T160,560v-280q0,-66 47,-113t113,-47h480v440q0,33 -23.5,56.5T720,640L600,640v160q0,33 -23.5,56.5T520,880h-80ZM240,400h480v-200h-40v160h-80v-160h-40v80h-80v-80L320,200q-33,0 -56.5,23.5T240,280v120ZM240,560h480v-80L240,480v80ZM240,560v-80,80Z"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -46,6 +46,7 @@
<string name="file_not_exist">文件不存在</string>
<string name="io_exception">IO异常</string>
<string name="current_status_is">当前状态:</string>
<string name="start">开始</string>
<!--Permissions-->
<string name="click_to_activate">点击以激活</string>
@@ -478,11 +479,18 @@
<string name="about_desc">使用安卓的Device admin、Device owner、Profile owner全方位掌控你的设备</string>
<string name="user_guide">使用教程</string>
<string name="source_code">源代码</string>
<string name="theme">主题</string>
<string name="amoled_black">纯黑夜间主题</string>
<string name="blackTheme_desc">需要打开夜间模式</string>
<string name="security">安全</string>
<string name="lock_owndroid">锁定OwnDroid</string>
<string name="enable_bio_auth">使用生物识别</string>
<string name="authenticate">验证</string>
<string name="auth_on_start">在OwnDroid启动时使用锁屏密码或生物识别进行验证</string>
<string name="use_password">使用密码</string>
<string name="auth_with_password">使用密码进行验证</string>
<string name="auth_with_bio">使用生物识别进行验证</string>
<!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">读取外部存储</string>

View File

@@ -49,6 +49,7 @@
<string name="file_not_exist">File not exist</string>
<string name="io_exception">IO Exception</string>
<string name="current_status_is">Current status:&#160;</string>
<string name="start">Start</string>
<!--Permissions-->
<string name="click_to_activate">Click to activate</string>
@@ -493,11 +494,18 @@
<string name="about_desc">Use Device admin, Profile owner and Device owner privilege to take full control of your device. </string>
<string name="user_guide">User guide</string>
<string name="source_code">Source code</string>
<string name="theme">Theme</string>
<string name="amoled_black">Black theme</string>
<string name="blackTheme_desc">Require dark mode on</string>
<string name="security">Security</string>
<string name="lock_owndroid">Lock OwnDroid</string>
<string name="enable_bio_auth">Auth with biometrics</string>
<string name="authenticate">Authenticate</string>
<string name="auth_on_start">Authenticating with keyguard password or biometrics when OwnDroid launch</string>
<string name="use_password">Use password</string>
<string name="auth_with_password">Authenticate OwnDroid with password</string>
<string name="auth_with_bio">Authenticate OwnDroid with biometrics</string>
<!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">Read external storage</string>

View File

@@ -3,6 +3,5 @@
<style name="Theme.OwnDroid" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:navigationBarColor">#FFFFFF</item>
<item name="android:statusBarColor">#FFFFFF</item>
<item name="android:windowSwipeToDismiss">true</item>
</style>
</resources>