diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b8904ae..b14504d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { minSdk = 21 targetSdk = 34 versionCode = 28 - versionName = "5.3" + versionName = "5.3.1" 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 2305b26..726b17c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Auth.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Auth.kt @@ -1,131 +1,117 @@ 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 androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt.AuthenticationCallback import androidx.biometric.BiometricPrompt.PromptInfo.Builder +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -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.draw.alpha +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import com.bintianqi.owndroid.ui.theme.OwnDroidTheme -import kotlinx.coroutines.Dispatchers +import androidx.fragment.app.FragmentActivity +import com.bintianqi.owndroid.ui.Animations import kotlinx.coroutines.delay import kotlinx.coroutines.launch -class AuthFragment: Fragment() { - @SuppressLint("UnrememberedMutableState") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val context = requireContext() - val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)!! - val canStartAuth = mutableStateOf(true) - val onAuthSucceed = { - val fragmentManager = this.parentFragmentManager - val transaction = fragmentManager.beginTransaction() - transaction.setCustomAnimations(R.anim.enter, R.anim.exit) - transaction.remove(this@AuthFragment).commit() - } - 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() { - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - onAuthSucceed() - } - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - when(errorCode){ - BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed() - BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true - else -> canStartAuth.value = true - } - Log.e("OwnDroid", errString.toString()) - } - } - lifecycleScope.launch(Dispatchers.Main) { - while(true){ - if(fallback){ - val fallbackPromptInfo = Builder() - .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) - .setTitle(context.getText(R.string.authenticate)) - .setSubtitle(context.getText(R.string.auth_with_password)) - .setConfirmationRequired(true) - .build() - val executor = ContextCompat.getMainExecutor(requireContext()) - val biometricPrompt = BiometricPrompt(requireActivity(), executor, callback) - biometricPrompt.authenticate(fallbackPromptInfo) - break - } - delay(50) - } - } - return ComposeView(requireContext()).apply { - setContent { - val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true)) - val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false)) - OwnDroidTheme(materialYou.value, blackTheme.value) { - Auth(this@AuthFragment, promptInfo, callback, canStartAuth) - } - } - } - } -} - @Composable -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 = stringResource(R.string.authenticate), - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onBackground - ) - LaunchedEffect(Unit){ +fun AuthScreen(activity: FragmentActivity, showAuth: MutableState){ + val context = activity.applicationContext + val coroutineScope = rememberCoroutineScope() + var canStartAuth by remember{ mutableStateOf(true) } + var fallback by remember{ mutableStateOf(false) } + var startFade by remember{ mutableStateOf(false) } + val alpha by animateFloatAsState( + targetValue = if(startFade) 0F else 1F, + label = "AuthScreenFade", + animationSpec = Animations.authScreenFade + ) + val onAuthSucceed = { + startFade = true + coroutineScope.launch { delay(300) - startAuth(activity, promptInfo, callback) - canStartAuth.value = false + showAuth.value = false } - Button( - onClick = { - startAuth(activity, promptInfo, callback) - canStartAuth.value = false - }, - enabled = canStartAuth.value + } + val promptInfo = Builder() + .setTitle(context.getText(R.string.authenticate)) + .setSubtitle(context.getText(R.string.auth_with_bio)) + .setConfirmationRequired(true) + val callback = object: AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + onAuthSucceed() + } + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + when(errorCode){ + BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed() + BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true + else -> canStartAuth = true + } + } + } + LaunchedEffect(fallback) { + if(fallback){ + val fallbackPromptInfo = promptInfo + .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .setSubtitle(context.getText(R.string.auth_with_password)) + .build() + val executor = ContextCompat.getMainExecutor(context) + val biometricPrompt = BiometricPrompt(activity, executor, callback) + biometricPrompt.authenticate(fallbackPromptInfo) + } + } + Surface( + modifier = Modifier + .fillMaxSize() + .alpha(alpha) + .background(if(isSystemInDarkTheme()) Color.Black else Color.White) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) ){ - Text(text = stringResource(R.string.start)) + LaunchedEffect(Unit){ + delay(300) + startAuth(activity, promptInfo, callback) + canStartAuth = false + } + Text( + text = stringResource(R.string.authenticate), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onBackground + ) + Button( + onClick = { + startAuth(activity, promptInfo, callback) + canStartAuth = false + }, + enabled = canStartAuth + ){ + Text(text = stringResource(R.string.start)) + } } } } -private fun startAuth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback){ - val context = activity.requireContext() +private fun startAuth(activity: FragmentActivity, basicPromptInfo: Builder, callback: AuthenticationCallback){ + val context = activity.applicationContext + val promptInfo = basicPromptInfo val bioManager = BiometricManager.from(context) val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) if(sharedPref.getBoolean("bio_auth", false)){ diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 1fcb3e9..c0564fb 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -6,11 +6,9 @@ import android.content.ComponentName import android.content.Context import android.os.Build.VERSION import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import android.widget.Toast import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -29,14 +27,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat -import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -51,67 +47,43 @@ import java.util.Locale var backToHome = false @ExperimentalMaterial3Api class MainActivity : FragmentActivity() { - private var auth = false + private val showAuth = mutableStateOf(false) + @SuppressLint("UnrememberedMutableState") override fun onCreate(savedInstanceState: Bundle?) { registerActivityResult(this) enableEdgeToEdge() WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) - setContentView(R.layout.base) val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) - val fragmentManager = supportFragmentManager - val transaction = fragmentManager.beginTransaction() - transaction.add(R.id.base, HomeFragment(), "home") if(sharedPref.getBoolean("auth", false)){ - transaction.add(R.id.base, AuthFragment(), "auth") + showAuth.value = true } - transaction.commit() val locale = applicationContext.resources?.configuration?.locale zhCN = locale==Locale.SIMPLIFIED_CHINESE||locale==Locale.CHINESE||locale==Locale.CHINA + setContent { + val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true)) + val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false)) + OwnDroidTheme(materialYou.value, blackTheme.value){ + Home(materialYou, blackTheme) + if(showAuth.value){ + AuthScreen(this, showAuth) + } + } + } } override fun onResume() { super.onResume() - if(auth){ - val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) - if( - sharedPref.getBoolean("auth", false) && - sharedPref.getBoolean("lock_in_background", false) - ){ - val fragmentManager = supportFragmentManager - val fragment = fragmentManager.findFragmentByTag("auth") - if(fragment == null){ - val transaction = fragmentManager.beginTransaction() - transaction.setCustomAnimations(R.anim.enter, R.anim.exit) - transaction.add(R.id.base, AuthFragment(), "auth").commit() - } - } - auth = false + val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) + if( + sharedPref.getBoolean("auth", false) && + sharedPref.getBoolean("lock_in_background", false) + ){ + showAuth.value = true } } - override fun onRestart() { - super.onRestart() - auth = true - } -} - -class HomeFragment: Fragment() { - @OptIn(ExperimentalMaterial3Api::class) - @SuppressLint("UnrememberedMutableState") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val sharedPref = context?.getSharedPreferences("data", Context.MODE_PRIVATE)!! - return ComposeView(requireContext()).apply { - setContent { - val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true)) - val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false)) - OwnDroidTheme(materialYou.value, blackTheme.value){ - Home(materialYou, blackTheme) - } - } - } - } } @SuppressLint("UnrememberedMutableState") diff --git a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt new file mode 100644 index 0000000..11c6705 --- /dev/null +++ b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt @@ -0,0 +1,84 @@ +package com.bintianqi.owndroid + +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.view.WindowCompat +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import com.bintianqi.owndroid.ui.theme.OwnDroidTheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ManageSpaceActivity: FragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + WindowCompat.setDecorFitsSystemWindows(window, false) + super.onCreate(savedInstanceState) + val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) + val materialYou = sharedPref.getBoolean("material_you",true) + val blackTheme = sharedPref.getBoolean("black_theme", false) + setContent { + OwnDroidTheme(materialYou, blackTheme) { + ManageSpace() + } + } + } + + @Composable + fun ManageSpace() { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) + ) { + val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) + val protected = sharedPref.getBoolean("protect_storage", false) + if(protected){ + Text( + text = stringResource(R.string.storage_is_protected), + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.titleMedium + ) + Text( + text = stringResource(R.string.you_cant_clear_storage), + color = MaterialTheme.colorScheme.onBackground + ) + } + Button( + onClick = { + lifecycleScope.launch { + sharedPref.edit().clear().apply() + delay(2000) + finishAndRemoveTask() + } + Toast.makeText(applicationContext, R.string.clear_storage_success, Toast.LENGTH_SHORT).show() + }, + enabled = !protected, + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.onError, + containerColor = MaterialTheme.colorScheme.error + ), + modifier = Modifier.padding(top = 10.dp) + ) { + Text(text = stringResource(R.string.clear_storage)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/Setting.kt b/app/src/main/java/com/bintianqi/owndroid/Setting.kt index d274f15..a8aa3e4 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Setting.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Setting.kt @@ -112,7 +112,7 @@ private fun AuthSettings(){ { sharedPref.edit().putBoolean("bio_auth",it).apply() } ) SwitchItem( - R.string.lock_in_background, "", null, + R.string.lock_in_background, stringResource(R.string.developing), null, { sharedPref.getBoolean("lock_in_background",false) }, { sharedPref.edit().putBoolean("lock_in_background",it).apply() } ) diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Animations.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Animations.kt index 34359e7..19c9e4a 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Animations.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Animations.kt @@ -11,8 +11,10 @@ object Animations{ private val bezier = CubicBezierEasing(0.20f, 0.85f, 0.0f, 1f) - private val tween: FiniteAnimationSpec = tween(durationMillis = 650, easing = bezier, delayMillis = 50) - + private val tween: FiniteAnimationSpec = tween(durationMillis = 550, easing = bezier, delayMillis = 50) + + val authScreenFade: FiniteAnimationSpec = tween(durationMillis = 200, easing = LinearEasing) + val navHostEnterTransition: AnimatedContentTransitionScope.() -> EnterTransition = { fadeIn(tween(100, easing = LinearEasing)) + slideIntoContainer( diff --git a/app/src/main/res/anim/enter.xml b/app/src/main/res/anim/enter.xml deleted file mode 100644 index 56ebee1..0000000 --- a/app/src/main/res/anim/enter.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/app/src/main/res/anim/exit.xml b/app/src/main/res/anim/exit.xml deleted file mode 100644 index facb15f..0000000 --- a/app/src/main/res/anim/exit.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/base.xml b/app/src/main/res/layout/base.xml deleted file mode 100644 index b6d7e67..0000000 --- a/app/src/main/res/layout/base.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f85e4b..6662f77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,7 +38,7 @@ Apply Decide by user Unsupported - Developing + Developing function Unknown effect Options Copy Command