Skip to content

Commit 32b78a9

Browse files
committed
fix a serious bug in v5.3 (#20)
use composable function instead of fragment to display authenticate screen fix authenticate screen is transparent in light theme
1 parent 074b8d9 commit 32b78a9

File tree

10 files changed

+193
-183
lines changed

10 files changed

+193
-183
lines changed

Diff for: app/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ android {
2222
minSdk = 21
2323
targetSdk = 34
2424
versionCode = 28
25-
versionName = "5.3"
25+
versionName = "5.3.1"
2626
multiDexEnabled = false
2727
}
2828

Diff for: app/src/main/java/com/bintianqi/owndroid/Auth.kt

+82-96
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,117 @@
11
package com.bintianqi.owndroid
22

3-
import android.annotation.SuppressLint
43
import android.content.Context
5-
import android.os.Bundle
6-
import android.util.Log
7-
import android.view.LayoutInflater
8-
import android.view.View
9-
import android.view.ViewGroup
104
import androidx.biometric.BiometricManager
115
import androidx.biometric.BiometricPrompt
126
import androidx.biometric.BiometricPrompt.AuthenticationCallback
137
import androidx.biometric.BiometricPrompt.PromptInfo.Builder
8+
import androidx.compose.animation.core.animateFloatAsState
149
import androidx.compose.foundation.background
10+
import androidx.compose.foundation.isSystemInDarkTheme
1511
import androidx.compose.foundation.layout.Arrangement
1612
import androidx.compose.foundation.layout.Column
1713
import androidx.compose.foundation.layout.fillMaxSize
1814
import androidx.compose.material3.Button
1915
import androidx.compose.material3.MaterialTheme
16+
import androidx.compose.material3.Surface
2017
import androidx.compose.material3.Text
21-
import androidx.compose.runtime.Composable
22-
import androidx.compose.runtime.LaunchedEffect
23-
import androidx.compose.runtime.MutableState
24-
import androidx.compose.runtime.mutableStateOf
18+
import androidx.compose.runtime.*
2519
import androidx.compose.ui.Alignment
2620
import androidx.compose.ui.Modifier
27-
import androidx.compose.ui.platform.ComposeView
21+
import androidx.compose.ui.draw.alpha
22+
import androidx.compose.ui.graphics.Color
2823
import androidx.compose.ui.res.stringResource
2924
import androidx.core.content.ContextCompat
30-
import androidx.fragment.app.Fragment
31-
import androidx.lifecycle.lifecycleScope
32-
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
33-
import kotlinx.coroutines.Dispatchers
25+
import androidx.fragment.app.FragmentActivity
26+
import com.bintianqi.owndroid.ui.Animations
3427
import kotlinx.coroutines.delay
3528
import kotlinx.coroutines.launch
3629

37-
class AuthFragment: Fragment() {
38-
@SuppressLint("UnrememberedMutableState")
39-
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
40-
val context = requireContext()
41-
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)!!
42-
val canStartAuth = mutableStateOf(true)
43-
val onAuthSucceed = {
44-
val fragmentManager = this.parentFragmentManager
45-
val transaction = fragmentManager.beginTransaction()
46-
transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
47-
transaction.remove(this@AuthFragment).commit()
48-
}
49-
val promptInfo = Builder()
50-
.setTitle(context.getText(R.string.authenticate))
51-
.setSubtitle(context.getText(R.string.auth_with_bio))
52-
.setConfirmationRequired(true)
53-
var fallback = false
54-
val callback = object: AuthenticationCallback() {
55-
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
56-
super.onAuthenticationSucceeded(result)
57-
onAuthSucceed()
58-
}
59-
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
60-
super.onAuthenticationError(errorCode, errString)
61-
when(errorCode){
62-
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
63-
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
64-
else -> canStartAuth.value = true
65-
}
66-
Log.e("OwnDroid", errString.toString())
67-
}
30+
@Composable
31+
fun AuthScreen(activity: FragmentActivity, showAuth: MutableState<Boolean>){
32+
val context = activity.applicationContext
33+
val coroutineScope = rememberCoroutineScope()
34+
var canStartAuth by remember{ mutableStateOf(true) }
35+
var fallback by remember{ mutableStateOf(false) }
36+
var startFade by remember{ mutableStateOf(false) }
37+
val alpha by animateFloatAsState(
38+
targetValue = if(startFade) 0F else 1F,
39+
label = "AuthScreenFade",
40+
animationSpec = Animations.authScreenFade
41+
)
42+
val onAuthSucceed = {
43+
startFade = true
44+
coroutineScope.launch {
45+
delay(300)
46+
showAuth.value = false
6847
}
69-
lifecycleScope.launch(Dispatchers.Main) {
70-
while(true){
71-
if(fallback){
72-
val fallbackPromptInfo = Builder()
73-
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
74-
.setTitle(context.getText(R.string.authenticate))
75-
.setSubtitle(context.getText(R.string.auth_with_password))
76-
.setConfirmationRequired(true)
77-
.build()
78-
val executor = ContextCompat.getMainExecutor(requireContext())
79-
val biometricPrompt = BiometricPrompt(requireActivity(), executor, callback)
80-
biometricPrompt.authenticate(fallbackPromptInfo)
81-
break
82-
}
83-
delay(50)
84-
}
48+
}
49+
val promptInfo = Builder()
50+
.setTitle(context.getText(R.string.authenticate))
51+
.setSubtitle(context.getText(R.string.auth_with_bio))
52+
.setConfirmationRequired(true)
53+
val callback = object: AuthenticationCallback() {
54+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
55+
super.onAuthenticationSucceeded(result)
56+
onAuthSucceed()
8557
}
86-
return ComposeView(requireContext()).apply {
87-
setContent {
88-
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
89-
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
90-
OwnDroidTheme(materialYou.value, blackTheme.value) {
91-
Auth(this@AuthFragment, promptInfo, callback, canStartAuth)
92-
}
58+
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
59+
super.onAuthenticationError(errorCode, errString)
60+
when(errorCode){
61+
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
62+
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
63+
else -> canStartAuth = true
9364
}
9465
}
9566
}
96-
}
97-
98-
@Composable
99-
fun Auth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback, canStartAuth: MutableState<Boolean>) {
100-
Column(
101-
horizontalAlignment = Alignment.CenterHorizontally,
102-
verticalArrangement = Arrangement.Center,
103-
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
104-
){
105-
Text(
106-
text = stringResource(R.string.authenticate),
107-
style = MaterialTheme.typography.headlineLarge,
108-
color = MaterialTheme.colorScheme.onBackground
109-
)
110-
LaunchedEffect(Unit){
111-
delay(300)
112-
startAuth(activity, promptInfo, callback)
113-
canStartAuth.value = false
67+
LaunchedEffect(fallback) {
68+
if(fallback){
69+
val fallbackPromptInfo = promptInfo
70+
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
71+
.setSubtitle(context.getText(R.string.auth_with_password))
72+
.build()
73+
val executor = ContextCompat.getMainExecutor(context)
74+
val biometricPrompt = BiometricPrompt(activity, executor, callback)
75+
biometricPrompt.authenticate(fallbackPromptInfo)
11476
}
115-
Button(
116-
onClick = {
117-
startAuth(activity, promptInfo, callback)
118-
canStartAuth.value = false
119-
},
120-
enabled = canStartAuth.value
77+
}
78+
Surface(
79+
modifier = Modifier
80+
.fillMaxSize()
81+
.alpha(alpha)
82+
.background(if(isSystemInDarkTheme()) Color.Black else Color.White)
83+
) {
84+
Column(
85+
horizontalAlignment = Alignment.CenterHorizontally,
86+
verticalArrangement = Arrangement.Center,
87+
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
12188
){
122-
Text(text = stringResource(R.string.start))
89+
LaunchedEffect(Unit){
90+
delay(300)
91+
startAuth(activity, promptInfo, callback)
92+
canStartAuth = false
93+
}
94+
Text(
95+
text = stringResource(R.string.authenticate),
96+
style = MaterialTheme.typography.headlineLarge,
97+
color = MaterialTheme.colorScheme.onBackground
98+
)
99+
Button(
100+
onClick = {
101+
startAuth(activity, promptInfo, callback)
102+
canStartAuth = false
103+
},
104+
enabled = canStartAuth
105+
){
106+
Text(text = stringResource(R.string.start))
107+
}
123108
}
124109
}
125110
}
126111

127-
private fun startAuth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback){
128-
val context = activity.requireContext()
112+
private fun startAuth(activity: FragmentActivity, basicPromptInfo: Builder, callback: AuthenticationCallback){
113+
val context = activity.applicationContext
114+
val promptInfo = basicPromptInfo
129115
val bioManager = BiometricManager.from(context)
130116
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
131117
if(sharedPref.getBoolean("bio_auth", false)){

Diff for: app/src/main/java/com/bintianqi/owndroid/MainActivity.kt

+20-48
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import android.content.ComponentName
66
import android.content.Context
77
import android.os.Build.VERSION
88
import android.os.Bundle
9-
import android.view.LayoutInflater
10-
import android.view.View
11-
import android.view.ViewGroup
129
import android.widget.Toast
1310
import androidx.activity.ComponentActivity
11+
import androidx.activity.compose.setContent
1412
import androidx.activity.enableEdgeToEdge
1513
import androidx.compose.foundation.background
1614
import androidx.compose.foundation.clickable
@@ -29,14 +27,12 @@ import androidx.compose.ui.Alignment
2927
import androidx.compose.ui.Modifier
3028
import androidx.compose.ui.draw.clip
3129
import androidx.compose.ui.input.pointer.pointerInput
32-
import androidx.compose.ui.platform.ComposeView
3330
import androidx.compose.ui.platform.LocalContext
3431
import androidx.compose.ui.platform.LocalFocusManager
3532
import androidx.compose.ui.res.painterResource
3633
import androidx.compose.ui.res.stringResource
3734
import androidx.compose.ui.unit.dp
3835
import androidx.core.view.WindowCompat
39-
import androidx.fragment.app.Fragment
4036
import androidx.fragment.app.FragmentActivity
4137
import androidx.navigation.NavHostController
4238
import androidx.navigation.compose.NavHost
@@ -51,67 +47,43 @@ import java.util.Locale
5147
var backToHome = false
5248
@ExperimentalMaterial3Api
5349
class MainActivity : FragmentActivity() {
54-
private var auth = false
50+
private val showAuth = mutableStateOf(false)
51+
5552
@SuppressLint("UnrememberedMutableState")
5653
override fun onCreate(savedInstanceState: Bundle?) {
5754
registerActivityResult(this)
5855
enableEdgeToEdge()
5956
WindowCompat.setDecorFitsSystemWindows(window, false)
6057
super.onCreate(savedInstanceState)
61-
setContentView(R.layout.base)
6258
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
63-
val fragmentManager = supportFragmentManager
64-
val transaction = fragmentManager.beginTransaction()
65-
transaction.add(R.id.base, HomeFragment(), "home")
6659
if(sharedPref.getBoolean("auth", false)){
67-
transaction.add(R.id.base, AuthFragment(), "auth")
60+
showAuth.value = true
6861
}
69-
transaction.commit()
7062
val locale = applicationContext.resources?.configuration?.locale
7163
zhCN = locale==Locale.SIMPLIFIED_CHINESE||locale==Locale.CHINESE||locale==Locale.CHINA
72-
}
73-
74-
override fun onResume() {
75-
super.onResume()
76-
if(auth){
77-
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
78-
if(
79-
sharedPref.getBoolean("auth", false) &&
80-
sharedPref.getBoolean("lock_in_background", false)
81-
){
82-
val fragmentManager = supportFragmentManager
83-
val fragment = fragmentManager.findFragmentByTag("auth")
84-
if(fragment == null){
85-
val transaction = fragmentManager.beginTransaction()
86-
transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
87-
transaction.add(R.id.base, AuthFragment(), "auth").commit()
64+
setContent {
65+
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
66+
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
67+
OwnDroidTheme(materialYou.value, blackTheme.value){
68+
Home(materialYou, blackTheme)
69+
if(showAuth.value){
70+
AuthScreen(this, showAuth)
8871
}
8972
}
90-
auth = false
9173
}
9274
}
9375

94-
override fun onRestart() {
95-
super.onRestart()
96-
auth = true
97-
}
98-
}
99-
100-
class HomeFragment: Fragment() {
101-
@OptIn(ExperimentalMaterial3Api::class)
102-
@SuppressLint("UnrememberedMutableState")
103-
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
104-
val sharedPref = context?.getSharedPreferences("data", Context.MODE_PRIVATE)!!
105-
return ComposeView(requireContext()).apply {
106-
setContent {
107-
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
108-
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
109-
OwnDroidTheme(materialYou.value, blackTheme.value){
110-
Home(materialYou, blackTheme)
111-
}
112-
}
76+
override fun onResume() {
77+
super.onResume()
78+
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
79+
if(
80+
sharedPref.getBoolean("auth", false) &&
81+
sharedPref.getBoolean("lock_in_background", false)
82+
){
83+
showAuth.value = true
11384
}
11485
}
86+
11587
}
11688

11789
@SuppressLint("UnrememberedMutableState")

0 commit comments

Comments
 (0)