diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a95e4563..57c3ceab 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,9 +31,14 @@
android:name=".ui.generate.GeneratePasswordActivity"
android:exported="true" />
+
+ android:windowSoftInputMode="adjustPan">
@@ -44,15 +49,6 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
-
-
(null)
+ }
+
+ BackHandler(state is AuthState.ConfirmPassword) {
+ dispatchAction(NavigationAction(AuthState.CreatePassword))
+ }
+
+ LaunchedEffect(key1 = Unit, block = {
+ coroutineScope.launch {
+ val mPassword = context.getKeyPassPassword()
+ if (mPassword == null) {
+ dispatchAction(NavigationAction(AuthState.CreatePassword))
+ }
+ }
+ })
+
+ Column(
+ modifier = Modifier
+ .padding(32.dp)
+ .fillMaxSize(1f)
+ .verticalScroll(rememberScrollState()),
+ Arrangement.SpaceEvenly,
+ Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(R.drawable.ic_undraw_unlock_24mb),
+ contentDescription = ""
+ )
+
+ Text(text = stringResource(id = state.title))
+
+ PasswordInputField(
+ password,
+ setPassword,
+ passwordVisible,
+ setPasswordVisible,
+ passwordError
+ )
+
+ ButtonBar(state, password, setPasswordError) {
+ dispatchAction(it)
+ }
+ }
+}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt
deleted file mode 100644
index 0f6e0541..00000000
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt
+++ /dev/null
@@ -1,215 +0,0 @@
-package com.yogeshpaliyal.keypass.ui.auth
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.compose.BackHandler
-import androidx.activity.compose.setContent
-import androidx.annotation.StringRes
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.Visibility
-import androidx.compose.material.icons.rounded.VisibilityOff
-import androidx.compose.material3.Button
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.text.input.VisualTransformation
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.yogeshpaliyal.common.utils.getKeyPassPassword
-import com.yogeshpaliyal.common.utils.setKeyPassPassword
-import com.yogeshpaliyal.keypass.R
-import com.yogeshpaliyal.keypass.ui.nav.DashboardComposeActivity
-import com.yogeshpaliyal.keypass.ui.style.KeyPassTheme
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
-
-private const val AUTHENTICATION_RESULT = 707
-
-sealed class AuthState(@StringRes val title: Int) {
- object CreatePassword : AuthState(R.string.create_password)
- class ConfirmPassword(val password: String) : AuthState(R.string.confirm_password)
- object Login : AuthState(R.string.login_to_enter_keypass)
-}
-
-@AndroidEntryPoint
-class AuthenticationActivity : AppCompatActivity() {
-
- @Preview(showSystemUi = true)
- @Composable
- fun AuthScreenPreview() {
- AuthScreen()
- }
-
- @Composable
- fun AuthScreen() {
- val context = LocalContext.current
-
- val coroutineScope = rememberCoroutineScope()
-
- val (state, setState) = remember {
- mutableStateOf(AuthState.Login)
- }
-
- val (password, setPassword) = remember(state) {
- mutableStateOf("")
- }
-
- val (passwordVisible, setPasswordVisible) = remember(state) { mutableStateOf(false) }
-
- val (passwordError, setPasswordError) = remember(state, password) {
- mutableStateOf(null)
- }
-
- BackHandler(state is AuthState.ConfirmPassword) {
- setState(AuthState.CreatePassword)
- }
-
- LaunchedEffect(key1 = Unit, block = {
- coroutineScope.launch {
- val mPassword = context.getKeyPassPassword()
- if (mPassword == null) {
- setState(AuthState.CreatePassword)
- }
- }
- })
-
- KeyPassTheme {
- Column(
- modifier = Modifier
- .padding(32.dp)
- .fillMaxSize(1f)
- .verticalScroll(rememberScrollState()),
- Arrangement.SpaceEvenly,
- Alignment.CenterHorizontally
- ) {
- Image(
- painter = painterResource(R.drawable.ic_undraw_unlock_24mb),
- contentDescription = ""
- )
-
- Text(text = stringResource(id = state.title))
-
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(1f),
- value = password,
- singleLine = true,
- placeholder = {
- Text(text = stringResource(id = R.string.enter_password))
- },
- visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
- onValueChange = setPassword,
- isError = passwordError != null,
- supportingText = {
- if (passwordError != null) {
- Text(
- modifier = Modifier.fillMaxWidth(),
- text = stringResource(id = passwordError),
- color = MaterialTheme.colorScheme.error
- )
- }
- },
- trailingIcon = {
- val image = if (passwordVisible) {
- Icons.Rounded.Visibility
- } else Icons.Rounded.VisibilityOff
-
- // Please provide localized description for accessibility services
- val description = if (passwordVisible) "Hide password" else "Show password"
-
- IconButton(onClick = { setPasswordVisible(!passwordVisible) }) {
- Icon(imageVector = image, description)
- }
- }
- )
-
- Row(modifier = Modifier.fillMaxWidth(1f), Arrangement.SpaceEvenly) {
- AnimatedVisibility(state is AuthState.ConfirmPassword) {
- Button(onClick = {
- setState(AuthState.CreatePassword)
- }) {
- Text(text = stringResource(id = R.string.back))
- }
- }
-
- Button(onClick = {
- when (state) {
- is AuthState.CreatePassword -> {
- if (password.isBlank()) {
- setPasswordError(R.string.enter_password)
- } else {
- setState(AuthState.ConfirmPassword(password))
- }
- }
-
- is AuthState.ConfirmPassword -> {
- if (state.password == password) {
- coroutineScope.launch {
- context.setKeyPassPassword(password)
- onAuthComplete(context)
- }
- } else {
- setPasswordError(R.string.password_no_match)
- }
- }
-
- is AuthState.Login -> {
- coroutineScope.launch {
- val savedPassword = context.getKeyPassPassword()
- if (savedPassword == password) {
- onAuthComplete(context)
- } else {
- setPasswordError(R.string.incorrect_password)
- }
- }
- }
- }
- }) {
- Text(text = stringResource(id = R.string.str_continue))
- }
- }
- }
- }
- }
-
- private fun onAuthComplete(context: Context) {
- // binding.passCodeView.isVisible = false
- val dashboardIntent = Intent(context, DashboardComposeActivity::class.java)
- startActivity(dashboardIntent)
- finish()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- AuthScreen()
- }
- }
-}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt
new file mode 100644
index 00000000..241c557a
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt
@@ -0,0 +1,77 @@
+package com.yogeshpaliyal.keypass.ui.auth.components
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.yogeshpaliyal.common.utils.getKeyPassPassword
+import com.yogeshpaliyal.common.utils.setKeyPassPassword
+import com.yogeshpaliyal.keypass.R
+import com.yogeshpaliyal.keypass.ui.redux.AuthState
+import com.yogeshpaliyal.keypass.ui.redux.HomeState
+import com.yogeshpaliyal.keypass.ui.redux.NavigationAction
+import kotlinx.coroutines.launch
+
+@Composable
+fun ButtonBar(
+ state: AuthState,
+ password: String,
+ setPasswordError: (Int?) -> Unit,
+ dispatchAction: (NavigationAction) -> Unit
+) {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+
+ Row(modifier = Modifier.fillMaxWidth(1f), Arrangement.SpaceEvenly) {
+ AnimatedVisibility(state is AuthState.ConfirmPassword) {
+ Button(onClick = {
+ dispatchAction(NavigationAction(AuthState.CreatePassword))
+ }) {
+ Text(text = stringResource(id = R.string.back))
+ }
+ }
+
+ Button(onClick = {
+ when (state) {
+ is AuthState.CreatePassword -> {
+ if (password.isBlank()) {
+ setPasswordError(R.string.enter_password)
+ } else {
+ dispatchAction(NavigationAction(AuthState.ConfirmPassword(password)))
+ }
+ }
+
+ is AuthState.ConfirmPassword -> {
+ if (state.password == password) {
+ coroutineScope.launch {
+ context.setKeyPassPassword(password)
+ dispatchAction(NavigationAction(HomeState(), true))
+ }
+ } else {
+ setPasswordError(R.string.password_no_match)
+ }
+ }
+
+ is AuthState.Login -> {
+ coroutineScope.launch {
+ val savedPassword = context.getKeyPassPassword()
+ if (savedPassword == password) {
+ dispatchAction(NavigationAction(HomeState(), true))
+ } else {
+ setPasswordError(R.string.incorrect_password)
+ }
+ }
+ }
+ }
+ }) {
+ Text(text = stringResource(id = R.string.str_continue))
+ }
+ }
+}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt
new file mode 100644
index 00000000..bd2e2828
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt
@@ -0,0 +1,62 @@
+package com.yogeshpaliyal.keypass.ui.auth.components
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Visibility
+import androidx.compose.material.icons.rounded.VisibilityOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import com.yogeshpaliyal.keypass.R
+
+@Composable
+fun PasswordInputField(
+ password: String,
+ setPassword: (String) -> Unit,
+ passwordVisible: Boolean,
+ setPasswordVisible: (Boolean) -> Unit,
+ passwordError: Int?
+) {
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(1f),
+ value = password,
+ singleLine = true,
+ placeholder = {
+ Text(text = stringResource(id = R.string.enter_password))
+ },
+ visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+ onValueChange = setPassword,
+ isError = passwordError != null,
+ supportingText = {
+ if (passwordError != null) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(id = passwordError),
+ color = MaterialTheme.colorScheme.error
+ )
+ }
+ },
+ trailingIcon = {
+ val image = if (passwordVisible) {
+ Icons.Rounded.Visibility
+ } else Icons.Rounded.VisibilityOff
+
+ // Please provide localized description for accessibility services
+ val description = if (passwordVisible) "Hide password" else "Show password"
+
+ IconButton(onClick = { setPasswordVisible(!passwordVisible) }) {
+ Icon(imageVector = image, description)
+ }
+ }
+ )
+}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt
index ce7f52a2..5b9a2ff6 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt
@@ -1,10 +1,13 @@
package com.yogeshpaliyal.keypass.ui.home
import android.app.Application
+import android.content.ContentResolver
+import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.data.AccountModel
+import com.yogeshpaliyal.common.dbhelper.restoreBackup
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -27,7 +30,12 @@ class DashboardViewModel @Inject constructor(
val mediator = MediatorLiveData>()
- fun queryUpdated(keyword: String?, tag: String?, sortField: String?, sortAscending: Boolean = true) {
+ fun queryUpdated(
+ keyword: String?,
+ tag: String?,
+ sortField: String?,
+ sortAscending: Boolean = true
+ ) {
viewModelScope.launch(Dispatchers.IO) {
if (sortAscending) {
mediator.postValue(appDao.getAllAccountsAscending(keyword ?: "", tag, sortField))
@@ -36,4 +44,12 @@ class DashboardViewModel @Inject constructor(
}
}
}
+
+ suspend fun restoreBackup(
+ keyphrase: String,
+ contentResolver: ContentResolver,
+ fileUri: Uri?
+ ): Boolean {
+ return appDb.restoreBackup(keyphrase, contentResolver, fileUri)
+ }
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt
index 1d9f077f..8f92a013 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt
@@ -1,5 +1,6 @@
package com.yogeshpaliyal.keypass.ui.home
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -67,14 +68,14 @@ fun Homepage(
)
}
- if (tag != null) {
+ AnimatedVisibility(tag != null) {
LazyRow(
modifier = Modifier.padding(vertical = 8.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
content = {
item {
AssistChip(onClick = { }, label = {
- Text(text = tag)
+ Text(text = tag ?: "")
}, trailingIcon = {
IconButton(onClick = {
dispatchAction(NavigationAction(HomeState(), true))
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/AccountsList.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/AccountsList.kt
index 00e3dadc..f7e85e5f 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/AccountsList.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/AccountsList.kt
@@ -1,10 +1,13 @@
package com.yogeshpaliyal.keypass.ui.home.components
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -53,31 +56,35 @@ import kotlinx.coroutines.delay
import org.reduxkotlin.compose.rememberDispatcher
import kotlin.time.Duration.Companion.seconds
+@OptIn(ExperimentalFoundationApi::class, ExperimentalAnimationApi::class)
@Composable
fun AccountsList(accounts: List? = null) {
val dispatch = rememberDispatcher()
if (accounts?.isNotEmpty() == true) {
- LazyColumn(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = 16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- items(accounts) { account ->
- Account(
- account,
- onClick = {
- if (it.type == AccountType.TOTP) {
- dispatch(IntentNavigation.AddTOTP(it.uniqueId))
- } else {
- dispatch(NavigationAction(AccountDetailState(it.id)))
+ AnimatedContent(targetState = accounts) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ items(it) { account ->
+ Account(
+ modifier = Modifier.animateItemPlacement(),
+ account,
+ onClick = {
+ if (it.type == AccountType.TOTP) {
+ dispatch(IntentNavigation.AddTOTP(it.uniqueId))
+ } else {
+ dispatch(NavigationAction(AccountDetailState(it.id)))
+ }
}
- }
- )
- }
- item {
- Spacer(modifier = Modifier.height(8.dp))
+ )
+ }
+ item {
+ Spacer(modifier = Modifier.height(8.dp))
+ }
}
}
} else {
@@ -87,12 +94,14 @@ fun AccountsList(accounts: List? = null) {
@Composable
fun Account(
+ modifier: Modifier,
accountModel: AccountModel,
onClick: (AccountModel) -> Unit
) {
val dispatch = rememberDispatcher()
Card(
+ modifier = modifier,
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
onClick = { onClick(accountModel) }
) {
@@ -218,7 +227,8 @@ fun WrapWithProgress(accountModel: AccountModel) {
return
}
- val infiniteTransition = rememberInfiniteTransition(accountModel.uniqueId ?: accountModel.title ?: "")
+ val infiniteTransition =
+ rememberInfiniteTransition(accountModel.uniqueId ?: accountModel.title ?: "")
val rotationAnimation = infiniteTransition.animateFloat(
initialValue = 1f - (accountModel.getTOtpProgress().toFloat() / 30),
targetValue = 1f,
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/SearchBar.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/SearchBar.kt
index ecf5e005..cbbf89d4 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/SearchBar.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/SearchBar.kt
@@ -1,5 +1,6 @@
package com.yogeshpaliyal.keypass.ui.home.components
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -40,7 +41,7 @@ fun SearchBar(
},
trailingIcon = {
Row {
- if (keyword.isNullOrBlank().not()) {
+ AnimatedVisibility(keyword.isNullOrBlank().not()) {
IconButton(onClick = { updateKeyword("") }) {
Icon(
painter = rememberVectorPainter(image = Icons.Rounded.Close),
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
index 8dcade06..e990f1a5 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
@@ -34,6 +34,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
@@ -45,12 +46,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidViewBinding
import com.yogeshpaliyal.keypass.BuildConfig
-import com.yogeshpaliyal.keypass.databinding.LayoutMySettingsFragmentBinding
+import com.yogeshpaliyal.keypass.ui.auth.AuthScreen
import com.yogeshpaliyal.keypass.ui.detail.AccountDetailPage
import com.yogeshpaliyal.keypass.ui.home.Homepage
import com.yogeshpaliyal.keypass.ui.redux.AccountDetailState
+import com.yogeshpaliyal.keypass.ui.redux.AuthState
import com.yogeshpaliyal.keypass.ui.redux.BottomSheetAction
import com.yogeshpaliyal.keypass.ui.redux.BottomSheetState
import com.yogeshpaliyal.keypass.ui.redux.GoBackAction
@@ -62,6 +63,7 @@ import com.yogeshpaliyal.keypass.ui.redux.ScreenState
import com.yogeshpaliyal.keypass.ui.redux.SettingsState
import com.yogeshpaliyal.keypass.ui.redux.TotpDetailState
import com.yogeshpaliyal.keypass.ui.redux.UpdateContextAction
+import com.yogeshpaliyal.keypass.ui.settings.MySettingCompose
import com.yogeshpaliyal.keypass.ui.style.KeyPassTheme
import dagger.hilt.android.AndroidEntryPoint
import org.reduxkotlin.compose.StoreProvider
@@ -101,9 +103,11 @@ fun Dashboard() {
dispatch(GoBackAction)
}
- if (systemBackPress) {
- (context as? AppCompatActivity)?.onBackPressed()
- }
+ LaunchedEffect(key1 = systemBackPress, block = {
+ if (systemBackPress) {
+ (context as? AppCompatActivity)?.onBackPressed()
+ }
+ })
DisposableEffect(KeyPassRedux, context) {
dispatch(UpdateContextAction(context))
@@ -128,27 +132,27 @@ fun Dashboard() {
fun CurrentPage() {
val currentScreen by selectState { this.currentScreen }
- when (currentScreen) {
- is HomeState -> {
- Homepage(homeState = (currentScreen as HomeState))
- }
+ currentScreen.let {
+ when (it) {
+ is HomeState -> {
+ Homepage(homeState = it)
+ }
- is SettingsState -> {
- MySettings()
- }
+ is SettingsState -> {
+ MySettingCompose()
+ }
- is AccountDetailState -> {
- AccountDetailPage(id = (currentScreen as AccountDetailState).accountId)
- }
+ is AccountDetailState -> {
+ AccountDetailPage(id = it.accountId)
+ }
- is TotpDetailState -> {
- }
- }
-}
+ is AuthState -> {
+ AuthScreen(it)
+ }
-@Composable
-fun MySettings() {
- AndroidViewBinding(LayoutMySettingsFragmentBinding::inflate) {
+ is TotpDetailState -> {
+ }
+ }
}
}
@@ -156,9 +160,9 @@ fun MySettings() {
fun OptionBottomBar(
viewModel: BottomNavViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
) {
- val bottomSheetState by selectState { this.bottomSheet }
+ val bottomSheetState by selectState { this.bottomSheet }
- if (!bottomSheetState.isBottomSheetOpen) {
+ if (bottomSheetState?.isBottomSheetOpen != true) {
return
}
@@ -230,7 +234,9 @@ fun NavItem(item: NavigationModelItem.NavMenuItem, onClick: () -> Unit) {
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = true),
onClick = onClick
- ).padding(16.dp).fillMaxWidth(1f)
+ )
+ .padding(16.dp)
+ .fillMaxWidth(1f)
) {
Icon(
painter = painterResource(id = item.icon),
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/Action.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/Action.kt
index c9585946..fab02cae 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/Action.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/Action.kt
@@ -15,6 +15,8 @@ data class StateUpdateAction(val state: ScreenState) : Action
sealed interface IntentNavigation : Action {
object GeneratePassword : IntentNavigation
+ object BackupActivity : IntentNavigation
+ object ShareApp : IntentNavigation
data class AddTOTP(val accountId: String? = null) : IntentNavigation
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt
index 6fe74d74..0fb222dc 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt
@@ -5,8 +5,10 @@ import android.content.ClipboardManager
import android.content.Intent
import android.widget.Toast
import androidx.core.content.ContextCompat
+import com.yogeshpaliyal.keypass.BuildConfig
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.addTOTP.AddTOTPActivity
+import com.yogeshpaliyal.keypass.ui.backup.BackupActivity
import com.yogeshpaliyal.keypass.ui.generate.GeneratePasswordActivity
import org.reduxkotlin.Reducer
import org.reduxkotlin.applyMiddleware
@@ -56,6 +58,13 @@ object KeyPassRedux {
state.copy(context = action.context)
}
+ is ToastAction -> {
+ state.context?.let {
+ Toast.makeText(it, action.text, Toast.LENGTH_SHORT).show()
+ }
+ state
+ }
+
is GoBackAction -> {
val lastItem = arrPages.removeLastOrNull()
if (lastItem != null) {
@@ -91,6 +100,21 @@ object KeyPassRedux {
is IntentNavigation.AddTOTP -> {
AddTOTPActivity.start(state.context, action.accountId)
}
+
+ is IntentNavigation.BackupActivity -> {
+ BackupActivity.start(state.context)
+ }
+
+ is IntentNavigation.ShareApp -> {
+ val sendIntent = Intent()
+ sendIntent.action = Intent.ACTION_SEND
+ sendIntent.putExtra(
+ Intent.EXTRA_TEXT,
+ "KeyPass Password Manager\n Offline, Secure, Open Source https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID
+ )
+ sendIntent.type = "text/plain"
+ state.context?.startActivity(Intent.createChooser(sendIntent, state.context.getString(R.string.share_keypass)))
+ }
}
next(action)
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/State.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/State.kt
index 08cf046b..f38fc668 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/State.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/State.kt
@@ -2,11 +2,13 @@ package com.yogeshpaliyal.keypass.ui.redux
import android.content.Context
import android.os.Bundle
+import androidx.annotation.StringRes
+import com.yogeshpaliyal.keypass.R
data class KeyPassState(
val context: Context? = null,
val currentScreen: ScreenState,
- val bottomSheet: BottomSheetState,
+ val bottomSheet: BottomSheetState? = null,
val systemBackPress: Boolean = false
)
@@ -17,6 +19,11 @@ data class AccountDetailState(val accountId: Long? = null) : ScreenState()
data class TotpDetailState(val accountId: String? = null) : ScreenState()
object SettingsState : ScreenState(true)
+open class AuthState(@StringRes val title: Int) : ScreenState(false) {
+ object CreatePassword : AuthState(R.string.create_password)
+ class ConfirmPassword(val password: String) : AuthState(R.string.confirm_password)
+ object Login : AuthState(R.string.login_to_enter_keypass)
+}
data class BottomSheetState(
val path: String,
val args: Bundle? = null,
@@ -24,7 +31,7 @@ data class BottomSheetState(
)
fun generateDefaultState(): KeyPassState {
- val currentPage = HomeState()
+ val currentPage = AuthState.Login
val bottomSheet = BottomSheetState(BottomSheetRoutes.HOME_NAV_MENU, isBottomSheetOpen = false)
return KeyPassState(currentScreen = currentPage, bottomSheet = bottomSheet)
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
index 674ba643..0373554e 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
@@ -1,39 +1,59 @@
package com.yogeshpaliyal.keypass.ui.settings
-import android.app.Activity
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.ContentResolver
-import android.content.DialogInterface
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat.getSystemService
-import androidx.documentfile.provider.DocumentFile
-import androidx.lifecycle.lifecycleScope
-import androidx.preference.Preference
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.annotation.StringRes
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Feedback
+import androidx.compose.material.icons.rounded.Share
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Divider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.preference.PreferenceFragmentCompat
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.yogeshpaliyal.common.dbhelper.createBackup
-import com.yogeshpaliyal.common.dbhelper.restoreBackup
import com.yogeshpaliyal.common.utils.BACKUP_KEY_LENGTH
import com.yogeshpaliyal.common.utils.email
-import com.yogeshpaliyal.common.utils.getOrCreateBackupKey
-import com.yogeshpaliyal.common.utils.setBackupDirectory
-import com.yogeshpaliyal.keypass.BuildConfig
import com.yogeshpaliyal.keypass.R
-import com.yogeshpaliyal.keypass.databinding.LayoutBackupKeypharseBinding
-import com.yogeshpaliyal.keypass.databinding.LayoutRestoreKeypharseBinding
-import com.yogeshpaliyal.keypass.ui.backup.BackupActivity
+import com.yogeshpaliyal.keypass.ui.home.DashboardViewModel
+import com.yogeshpaliyal.keypass.ui.redux.Action
+import com.yogeshpaliyal.keypass.ui.redux.IntentNavigation
+import com.yogeshpaliyal.keypass.ui.redux.ToastAction
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
+import org.reduxkotlin.compose.rememberTypedDispatcher
import javax.inject.Inject
-private const val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
-private const val CHOOSE_RESTORE_FILE_REQUEST_CODE = 26213
-
@AndroidEntryPoint
class MySettingsFragment : PreferenceFragmentCompat() {
@@ -43,202 +63,181 @@ class MySettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
+}
- override fun onPreferenceTreeClick(preference: Preference): Boolean {
- return when (preference.key) {
- "feedback" -> {
- context?.email(
- getString(R.string.feedback_to_keypass),
- "yogeshpaliyal.foss@gmail.com"
- )
- true
- }
-
- "backup" -> {
- BackupActivity.start(context)
- true
- }
-
- getString(R.string.settings_restore_backup) -> {
- selectRestoreFile()
- true
- }
-
- "share" -> {
- val sendIntent = Intent()
- sendIntent.action = Intent.ACTION_SEND
- sendIntent.putExtra(
- Intent.EXTRA_TEXT,
- "KeyPass Password Manager\n Offline, Secure, Open Source https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID
- )
- sendIntent.type = "text/plain"
- startActivity(Intent.createChooser(sendIntent, getString(R.string.share_keypass)))
- true
- }
- else -> super.onPreferenceTreeClick(preference)
- }
+@Composable
+fun RestoreDialog(
+ selectedFile: Uri,
+ hideDialog: () -> Unit,
+ mViewModel: DashboardViewModel = hiltViewModel()
+) {
+ val (keyphrase, setKeyPhrase) = remember {
+ mutableStateOf("")
}
- private fun selectRestoreFile() {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
- intent.addCategory(Intent.CATEGORY_OPENABLE)
- intent.type = "*/*"
+ val dispatchAction = rememberTypedDispatcher()
- intent.addFlags(
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
- Intent.FLAG_GRANT_READ_URI_PERMISSION
+ val context = LocalContext.current
+
+ val coroutineScope = rememberCoroutineScope()
+
+ AlertDialog(
+ onDismissRequest = {
+ hideDialog()
+ },
+ title = {
+ Text(text = stringResource(id = R.string.restore))
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ if (keyphrase.isEmpty()) {
+ dispatchAction(ToastAction(R.string.alert_blank_keyphrase))
+ return@TextButton
+ }
+
+ if (keyphrase.length != BACKUP_KEY_LENGTH) {
+ dispatchAction(ToastAction(R.string.alert_invalid_keyphrase))
+ return@TextButton
+ }
+ coroutineScope.launch {
+ val result =
+ mViewModel.restoreBackup(keyphrase, context.contentResolver, selectedFile)
+
+ if (result) {
+ hideDialog()
+ dispatchAction(ToastAction(R.string.backup_restored))
+ } else {
+ dispatchAction(ToastAction(R.string.invalid_keyphrase))
+ }
+ }
+ }) {
+ Text(text = stringResource(id = R.string.restore))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = hideDialog) {
+ Text(text = stringResource(id = R.string.cancel))
+ }
+ },
+ text = {
+ Column(modifier = Modifier.fillMaxWidth(1f)) {
+ Text(text = stringResource(id = R.string.keyphrase_restore_info))
+ Spacer(modifier = Modifier.size(8.dp))
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(1f),
+ value = keyphrase,
+ onValueChange = setKeyPhrase,
+ placeholder = {
+ Text(text = stringResource(id = R.string.enter_keyphrase))
+ }
+ )
+ }
+ }
+ )
+}
+
+@Preview(showSystemUi = true)
+@Composable
+fun MySettingCompose() {
+ val dispatchAction = rememberTypedDispatcher()
+ val context = LocalContext.current
+
+ val (result, setResult) = remember { mutableStateOf(null) }
+
+ val launcher = rememberLauncherForActivityResult(OpenKeyPassBackup()) {
+ setResult(it)
+ }
+
+ result?.let {
+ RestoreDialog(
+ selectedFile = it,
+ hideDialog = {
+ setResult(null)
+ }
)
-
- try {
- startActivityForResult(intent, CHOOSE_RESTORE_FILE_REQUEST_CODE)
- } catch (e: Exception) {
- e.printStackTrace()
- }
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
- val contentResolver = context?.contentResolver
- val selectedDirectory = data?.data
- if (contentResolver != null && selectedDirectory != null) {
- contentResolver.takePersistableUriPermission(
- selectedDirectory,
- Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- )
-
- lifecycleScope.launch {
- context?.setBackupDirectory(selectedDirectory.toString())
-
- backup(selectedDirectory)
- }
- }
- } else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
- val contentResolver = context?.contentResolver
- val selectedFile = data?.data
- if (contentResolver != null && selectedFile != null) {
- val binding = LayoutRestoreKeypharseBinding.inflate(layoutInflater)
-
- val dialog = MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
- .setNegativeButton(
- R.string.cancel
- ) { dialog, which ->
- dialog.dismiss()
- }
- .setPositiveButton(
- R.string.restore
- ) { dialog, which ->
- dialog.dismiss()
- }.create()
-
- dialog.setOnShowListener {
- val positiveBtn = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
- positiveBtn.setOnClickListener {
- restore(
- dialog,
- binding.etKeyPhrase.text.toString(),
- contentResolver,
- selectedFile
- )
- }
- }
- dialog.show()
- }
+ Column {
+ PreferenceItem(title = R.string.security, isCategory = true)
+ PreferenceItem(
+ title = R.string.credentials_backups,
+ summary = R.string.credentials_backups_desc
+ ) {
+ dispatchAction(IntentNavigation.BackupActivity)
+ }
+ PreferenceItem(
+ title = R.string.restore_credentials,
+ summary = R.string.restore_credentials_desc
+ ) {
+ launcher.launch(arrayOf())
+ }
+ Divider(
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .height(1.dp)
+ )
+ PreferenceItem(title = R.string.help, isCategory = true)
+ PreferenceItem(
+ title = R.string.send_feedback,
+ summary = R.string.send_feedback_desc,
+ icon = Icons.Rounded.Feedback
+ ) {
+ context.email(
+ context.getString(R.string.feedback_to_keypass),
+ "yogeshpaliyal.foss@gmail.com"
+ )
+ }
+ PreferenceItem(
+ title = R.string.share,
+ summary = R.string.share_desc,
+ icon = Icons.Rounded.Share
+ ) {
+ dispatchAction(IntentNavigation.ShareApp)
}
}
+}
- private fun restore(
- dialog: AlertDialog,
- keyphrase: String,
- contentResolver: ContentResolver,
- selectedFile: Uri
+@Composable
+fun PreferenceItem(
+ @StringRes title: Int,
+ @StringRes summary: Int? = null,
+ icon: ImageVector? = null,
+ isCategory: Boolean = false,
+ onClickItem: (() -> Unit)? = null
+) {
+ val titleColor = if (isCategory) {
+ MaterialTheme.colorScheme.secondary
+ } else {
+ Color.Unspecified
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .widthIn(48.dp)
+ .padding(horizontal = 16.dp)
+ .clickable(onClickItem != null) {
+ onClickItem?.invoke()
+ },
+ verticalAlignment = Alignment.CenterVertically
) {
- if (keyphrase.isEmpty()) {
- Toast.makeText(
- context,
- R.string.alert_blank_keyphrase,
- Toast.LENGTH_SHORT
- )
- .show()
- return
- }
-
- if (keyphrase.length != BACKUP_KEY_LENGTH) {
- Toast.makeText(
- context,
- R.string.alert_invalid_keyphrase,
- Toast.LENGTH_SHORT
- ).show()
- return
- }
- lifecycleScope.launch {
- val result = appDb.restoreBackup(
- keyphrase,
- contentResolver,
- selectedFile
- )
- if (result) {
- dialog.dismiss()
- Toast.makeText(
- context,
- getString(R.string.backup_restored),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- Toast.makeText(
- context,
- getString(R.string.invalid_keyphrase),
- Toast.LENGTH_SHORT
- ).show()
+ Box(modifier = Modifier.width(56.dp), Alignment.CenterStart) {
+ if (icon != null) {
+ Icon(painter = rememberVectorPainter(image = icon), contentDescription = "")
}
}
- }
-
- suspend fun backup(selectedDirectory: Uri) {
- val keyPair = requireContext().getOrCreateBackupKey()
-
- val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
- "*/*",
- "key_pass_backup_${System.currentTimeMillis()}.keypass"
- )
-
- lifecycleScope.launch {
- context?.contentResolver?.let {
- appDb.createBackup(
- keyPair.second,
- it,
- tempFile?.uri
- )
- if (keyPair.first) {
- val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
- binding.txtCode.text = requireContext().getOrCreateBackupKey().second
- binding.txtCode.setOnClickListener {
- val clipboard =
- getSystemService(requireContext(), ClipboardManager::class.java)
- val clip = ClipData.newPlainText(
- getString(R.string.app_name),
- binding.txtCode.text
- )
- clipboard?.setPrimaryClip(clip)
- Toast.makeText(
- context,
- getString(R.string.copied_to_clipboard),
- Toast.LENGTH_SHORT
- ).show()
- }
- MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
- .setPositiveButton(
- "Yes"
- ) { dialog, which ->
- dialog?.dismiss()
- }.show()
- } else {
- Toast.makeText(
- context,
- getString(R.string.backup_completed),
- Toast.LENGTH_SHORT
- ).show()
- }
+ Column(
+ modifier = Modifier
+ .padding(vertical = 16.dp)
+ .fillMaxWidth(1f)
+ ) {
+ Text(
+ text = stringResource(id = title),
+ color = titleColor,
+ style = TextStyle(fontSize = 16.sp)
+ )
+ if (summary != null) {
+ Text(text = stringResource(id = summary), style = TextStyle(fontSize = 14.sp))
}
}
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsViewModel.kt
new file mode 100644
index 00000000..447883ea
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsViewModel.kt
@@ -0,0 +1,14 @@
+package com.yogeshpaliyal.keypass.ui.settings
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class MySettingsViewModel @Inject constructor(
+ application: Application,
+ val appDb: com.yogeshpaliyal.common.AppDatabase
+) : AndroidViewModel(application) {
+ private val appDao = appDb.getDao()
+}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/OpenKeyPassBackup.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/OpenKeyPassBackup.kt
new file mode 100644
index 00000000..84b06eb5
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/OpenKeyPassBackup.kt
@@ -0,0 +1,20 @@
+package com.yogeshpaliyal.keypass.ui.settings
+
+import android.content.Context
+import android.content.Intent
+import androidx.activity.result.contract.ActivityResultContracts
+
+class OpenKeyPassBackup : ActivityResultContracts.OpenDocument() {
+ override fun createIntent(context: Context, input: Array): Intent {
+ super.createIntent(context, input)
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "*/*"
+
+ intent.addFlags(
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
+ return intent
+ }
+}