|
1 | 1 | package com.bintianqi.owndroid
|
2 | 2 |
|
3 |
| -import android.annotation.SuppressLint |
4 | 3 | 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 |
10 | 4 | import androidx.biometric.BiometricManager
|
11 | 5 | import androidx.biometric.BiometricPrompt
|
12 | 6 | import androidx.biometric.BiometricPrompt.AuthenticationCallback
|
13 | 7 | import androidx.biometric.BiometricPrompt.PromptInfo.Builder
|
| 8 | +import androidx.compose.animation.core.animateFloatAsState |
14 | 9 | import androidx.compose.foundation.background
|
| 10 | +import androidx.compose.foundation.isSystemInDarkTheme |
15 | 11 | import androidx.compose.foundation.layout.Arrangement
|
16 | 12 | import androidx.compose.foundation.layout.Column
|
17 | 13 | import androidx.compose.foundation.layout.fillMaxSize
|
18 | 14 | import androidx.compose.material3.Button
|
19 | 15 | import androidx.compose.material3.MaterialTheme
|
| 16 | +import androidx.compose.material3.Surface |
20 | 17 | 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.* |
25 | 19 | import androidx.compose.ui.Alignment
|
26 | 20 | 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 |
28 | 23 | import androidx.compose.ui.res.stringResource
|
29 | 24 | 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 |
34 | 27 | import kotlinx.coroutines.delay
|
35 | 28 | import kotlinx.coroutines.launch
|
36 | 29 |
|
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 |
68 | 47 | }
|
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() |
85 | 57 | }
|
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 |
93 | 64 | }
|
94 | 65 | }
|
95 | 66 | }
|
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) |
114 | 76 | }
|
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) |
121 | 88 | ){
|
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 | + } |
123 | 108 | }
|
124 | 109 | }
|
125 | 110 | }
|
126 | 111 |
|
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 |
129 | 115 | val bioManager = BiometricManager.from(context)
|
130 | 116 | val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
|
131 | 117 | if(sharedPref.getBoolean("bio_auth", false)){
|
|
0 commit comments