From a2082641b1932d73872cde565fc5cebfbb9aa088 Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Thu, 16 May 2024 21:17:53 +0800 Subject: [PATCH] optimize UI and simplify code of Authenticaion --- app/build.gradle.kts | 4 +- .../main/java/com/bintianqi/owndroid/Auth.kt | 126 ++++++++++-------- .../java/com/bintianqi/owndroid/Setting.kt | 37 +++-- app/src/main/res/anim/enter.xml | 18 +++ app/src/main/res/anim/exit.xml | 8 ++ .../main/res/drawable/format_paint_fill0.xml | 9 ++ app/src/main/res/values-zh-rCN/strings.xml | 8 ++ app/src/main/res/values/strings.xml | 8 ++ app/src/main/res/values/themes.xml | 1 - 9 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 app/src/main/res/anim/enter.xml create mode 100644 app/src/main/res/anim/exit.xml create mode 100644 app/src/main/res/drawable/format_paint_fill0.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5561787..c43adde 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 } diff --git a/app/src/main/java/com/bintianqi/owndroid/Auth.kt b/app/src/main/java/com/bintianqi/owndroid/Auth.kt index ccad6a8..7bc5594 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Auth.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Auth.kt @@ -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(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) { 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()) } diff --git a/app/src/main/java/com/bintianqi/owndroid/Setting.kt b/app/src/main/java/com/bintianqi/owndroid/Setting.kt index 2337fc9..ee79319 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Setting.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Setting.kt @@ -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, blackTheme: MutableState){ @@ -65,8 +59,8 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState, 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)) + } + } } } diff --git a/app/src/main/res/anim/enter.xml b/app/src/main/res/anim/enter.xml new file mode 100644 index 0000000..fd3f154 --- /dev/null +++ b/app/src/main/res/anim/enter.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/anim/exit.xml b/app/src/main/res/anim/exit.xml new file mode 100644 index 0000000..80a3c74 --- /dev/null +++ b/app/src/main/res/anim/exit.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/format_paint_fill0.xml b/app/src/main/res/drawable/format_paint_fill0.xml new file mode 100644 index 0000000..3ef2f9e --- /dev/null +++ b/app/src/main/res/drawable/format_paint_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0c73510..54066d4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -46,6 +46,7 @@ 文件不存在 IO异常 当前状态: + 开始 点击以激活 @@ -478,11 +479,18 @@ 使用安卓的Device admin、Device owner、Profile owner,全方位掌控你的设备 使用教程 源代码 + 主题 纯黑夜间主题 需要打开夜间模式 + 安全 锁定OwnDroid 使用生物识别 + 验证 + 在OwnDroid启动时使用锁屏密码或生物识别进行验证 + 使用密码 + 使用密码进行验证 + 使用生物识别进行验证 读取外部存储 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44c4853..28d2cd1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,6 +49,7 @@ File not exist IO Exception Current status:  + Start Click to activate @@ -493,11 +494,18 @@ Use Device admin, Profile owner and Device owner privilege to take full control of your device. User guide Source code + Theme Black theme Require dark mode on + Security Lock OwnDroid Auth with biometrics + Authenticate + Authenticating with keyguard password or biometrics when OwnDroid launch + Use password + Authenticate OwnDroid with password + Authenticate OwnDroid with biometrics Read external storage diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index c9379a0..55bb275 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -3,6 +3,5 @@