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" applicationId = "com.bintianqi.owndroid"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 27 versionCode = 28
versionName = "5.2" versionName = "5.3"
multiDexEnabled = false multiDexEnabled = false
} }

View File

@@ -3,14 +3,15 @@ package com.bintianqi.owndroid
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.FrameLayout
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback 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.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.Button
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView 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.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlinx.coroutines.* import kotlinx.coroutines.*
class AuthFragment: Fragment() { class AuthFragment: Fragment() {
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("UnrememberedMutableState") @SuppressLint("UnrememberedMutableState")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 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 onAuthSucceed = {
val fragmentManager = this.parentFragmentManager val fragmentManager = this.parentFragmentManager
val fragment = fragmentManager.findFragmentByTag("auth") val transaction = fragmentManager.beginTransaction()
if(fragment != null) { transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
val fragmentTransaction = fragmentManager.beginTransaction() transaction.add(R.id.base, homeFragment)
fragmentTransaction.replace(R.id.base, homeFragment) requireActivity().findViewById<FrameLayout>(R.id.base).bringChildToFront(homeFragment.view)
fragmentTransaction.commit() transaction.commit()
lifecycleScope.launch {
delay(500)
fragmentManager.beginTransaction().remove(this@AuthFragment).commit()
} }
} }
val promptInfo = PromptInfo.Builder() val promptInfo = Builder()
.setTitle("Auth") .setTitle(context.getText(R.string.authenticate))
.setSubtitle("Auth OwnDroid with password or biometric") .setSubtitle(context.getText(R.string.auth_with_bio))
.setConfirmationRequired(true) .setConfirmationRequired(true)
var fallback = false var fallback = false
val callback = object: AuthenticationCallback() { val callback = object: AuthenticationCallback() {
@@ -56,22 +60,26 @@ class AuthFragment: Fragment() {
} }
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString) super.onAuthenticationError(errorCode, errString)
if(errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) onAuthSucceed() when(errorCode){
if(errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) fallback = true BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
if(errorCode == BiometricPrompt.ERROR_CANCELED) return BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
Toast.makeText(context, errString, Toast.LENGTH_SHORT).show() else -> canStartAuth.value = true
}
Log.e("OwnDroid", errString.toString())
} }
} }
GlobalScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
while(true){ while(true){
if(fallback){ if(fallback){
val fallbackPromptInfo = PromptInfo.Builder() val fallbackPromptInfo = Builder()
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setTitle("Auth") .setTitle(context.getText(R.string.authenticate))
.setSubtitle("Auth OwnDroid with password") .setSubtitle(context.getText(R.string.auth_with_password))
.setConfirmationRequired(true) .setConfirmationRequired(true)
.build() .build()
authWithBiometricPrompt(requireActivity(), fallbackPromptInfo, callback) val executor = ContextCompat.getMainExecutor(requireContext())
val biometricPrompt = BiometricPrompt(requireActivity(), executor, callback)
biometricPrompt.authenticate(fallbackPromptInfo)
break break
} }
delay(50) delay(50)
@@ -82,7 +90,7 @@ class AuthFragment: Fragment() {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true)) val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false)) val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
OwnDroidTheme(materialYou.value, blackTheme.value) { OwnDroidTheme(materialYou.value, blackTheme.value) {
Auth(this@AuthFragment.requireActivity(), callback, promptInfo) Auth(this@AuthFragment, promptInfo, callback, canStartAuth)
} }
} }
} }
@@ -90,16 +98,35 @@ class AuthFragment: Fragment() {
} }
@Composable @Composable
fun Auth(activity: FragmentActivity, callback: AuthenticationCallback, promptInfo: PromptInfo.Builder) { fun Auth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback, canStartAuth: MutableState<Boolean>) {
val context = LocalContext.current
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) 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( Button(
onClick = { onClick = {
startAuth(activity, promptInfo, callback)
canStartAuth.value = false
},
enabled = canStartAuth.value
){
Text(text = stringResource(R.string.start))
}
}
}
private fun startAuth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback){
val context = activity.requireContext()
val bioManager = BiometricManager.from(context) val bioManager = BiometricManager.from(context)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPref.getBoolean("bio_auth", false)){ if(sharedPref.getBoolean("bio_auth", false)){
@@ -107,26 +134,21 @@ fun Auth(activity: FragmentActivity, callback: AuthenticationCallback, promptInf
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) -> bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
promptInfo promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG) .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setNegativeButtonText("Use password") .setNegativeButtonText(context.getText(R.string.use_password))
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) -> bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
promptInfo promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK) .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setNegativeButtonText("Use password") .setNegativeButtonText(context.getText(R.string.use_password))
else -> promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) else -> promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
} }
}else{ }else{
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
} }
authWithBiometricPrompt(activity, promptInfo.build(), callback) val executor = ContextCompat.getMainExecutor(context)
}
){
Text(text = "Start")
}
}
}
private fun authWithBiometricPrompt(activity: FragmentActivity, promptInfo: PromptInfo, callback: AuthenticationCallback) {
val executor = ContextCompat.getMainExecutor(activity.applicationContext)
val biometricPrompt = BiometricPrompt(activity, executor, callback) 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.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
@@ -24,10 +21,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
@Composable @Composable
fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, blackTheme: MutableState<Boolean>){ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, blackTheme: MutableState<Boolean>){
@@ -65,8 +59,8 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
@Composable @Composable
private fun Home(navCtrl: NavHostController){ private fun Home(navCtrl: NavHostController){
Column(modifier = Modifier.fillMaxSize()){ Column(modifier = Modifier.fillMaxSize()){
SubPageItem(R.string.setting,"",R.drawable.settings_fill0){navCtrl.navigate("Theme")} SubPageItem(R.string.theme,"",R.drawable.format_paint_fill0){navCtrl.navigate("Theme")}
SubPageItem(R.string.security,"",R.drawable.settings_fill0){navCtrl.navigate("Auth")} SubPageItem(R.string.security,"",R.drawable.lock_fill0){navCtrl.navigate("Auth")}
SubPageItem(R.string.about,"",R.drawable.info_fill0){navCtrl.navigate("About")} SubPageItem(R.string.about,"",R.drawable.info_fill0){navCtrl.navigate("About")}
} }
} }
@@ -103,7 +97,6 @@ private fun AuthSettings(){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) } var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
if(VERSION.SDK_INT>=30){
SwitchItem( SwitchItem(
R.string.lock_owndroid, "", null, R.string.lock_owndroid, "", null,
{ auth }, { auth },
@@ -112,7 +105,6 @@ private fun AuthSettings(){
auth = sharedPref.getBoolean("auth",false) auth = sharedPref.getBoolean("auth",false)
} }
) )
}
if(auth){ if(auth){
SwitchItem( SwitchItem(
R.string.enable_bio_auth, "", null, R.string.enable_bio_auth, "", null,
@@ -120,6 +112,11 @@ private fun AuthSettings(){
{ sharedPref.edit().putBoolean("bio_auth",it).apply() } { 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="file_not_exist">文件不存在</string>
<string name="io_exception">IO异常</string> <string name="io_exception">IO异常</string>
<string name="current_status_is">当前状态:</string> <string name="current_status_is">当前状态:</string>
<string name="start">开始</string>
<!--Permissions--> <!--Permissions-->
<string name="click_to_activate">点击以激活</string> <string name="click_to_activate">点击以激活</string>
@@ -478,11 +479,18 @@
<string name="about_desc">使用安卓的Device admin、Device owner、Profile owner全方位掌控你的设备</string> <string name="about_desc">使用安卓的Device admin、Device owner、Profile owner全方位掌控你的设备</string>
<string name="user_guide">使用教程</string> <string name="user_guide">使用教程</string>
<string name="source_code">源代码</string> <string name="source_code">源代码</string>
<string name="theme">主题</string>
<string name="amoled_black">纯黑夜间主题</string> <string name="amoled_black">纯黑夜间主题</string>
<string name="blackTheme_desc">需要打开夜间模式</string> <string name="blackTheme_desc">需要打开夜间模式</string>
<string name="security">安全</string> <string name="security">安全</string>
<string name="lock_owndroid">锁定OwnDroid</string> <string name="lock_owndroid">锁定OwnDroid</string>
<string name="enable_bio_auth">使用生物识别</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--> <!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">读取外部存储</string> <string name="permission_READ_EXTERNAL_STORAGE">读取外部存储</string>

View File

@@ -49,6 +49,7 @@
<string name="file_not_exist">File not exist</string> <string name="file_not_exist">File not exist</string>
<string name="io_exception">IO Exception</string> <string name="io_exception">IO Exception</string>
<string name="current_status_is">Current status:&#160;</string> <string name="current_status_is">Current status:&#160;</string>
<string name="start">Start</string>
<!--Permissions--> <!--Permissions-->
<string name="click_to_activate">Click to activate</string> <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="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="user_guide">User guide</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="theme">Theme</string>
<string name="amoled_black">Black theme</string> <string name="amoled_black">Black theme</string>
<string name="blackTheme_desc">Require dark mode on</string> <string name="blackTheme_desc">Require dark mode on</string>
<string name="security">Security</string> <string name="security">Security</string>
<string name="lock_owndroid">Lock OwnDroid</string> <string name="lock_owndroid">Lock OwnDroid</string>
<string name="enable_bio_auth">Auth with biometrics</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--> <!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">Read external storage</string> <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"> <style name="Theme.OwnDroid" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:navigationBarColor">#FFFFFF</item> <item name="android:navigationBarColor">#FFFFFF</item>
<item name="android:statusBarColor">#FFFFFF</item> <item name="android:statusBarColor">#FFFFFF</item>
<item name="android:windowSwipeToDismiss">true</item>
</style> </style>
</resources> </resources>