diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt b/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt index f5257530..ad609391 100644 --- a/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/onboarding/activity/OnboardingActivity.kt @@ -9,7 +9,9 @@ import android.os.Bundle import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import app.passwordstore.R +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt index 43dc7f4c..b5facaf3 100644 --- a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt @@ -5,21 +5,68 @@ package app.passwordstore.ui.onboarding.fragments +import android.content.SharedPreferences import android.os.Bundle import android.view.View +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import app.passwordstore.R +import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.databinding.FragmentKeySelectionBinding +import app.passwordstore.injection.prefs.SettingsPreferences +import app.passwordstore.ui.pgp.PGPKeyListActivity +import app.passwordstore.util.coroutines.DispatcherProvider +import app.passwordstore.util.extensions.commitChange +import app.passwordstore.util.extensions.finish +import app.passwordstore.util.extensions.snackbar import app.passwordstore.util.extensions.viewBinding +import app.passwordstore.util.settings.PreferenceKeys +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +import java.io.File +import javax.inject.Inject +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +@AndroidEntryPoint class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) { + @Inject @SettingsPreferences lateinit var settings: SharedPreferences + @Inject lateinit var dispatcherProvider: DispatcherProvider private val binding by viewBinding(FragmentKeySelectionBinding::bind) + private val gpgKeySelectAction = + registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + val data = result.data ?: return@registerForActivityResult + val selectedKey = + data.getStringExtra(PGPKeyListActivity.EXTRA_SELECTED_KEY) + ?: return@registerForActivityResult + lifecycleScope.launch { + withContext(dispatcherProvider.io()) { + val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id") + gpgIdentifierFile.writeText(selectedKey) + } + settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } + requireActivity() + .commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name))) + finish() + } + } else { + requireActivity() + .snackbar( + message = getString(R.string.gpg_key_select_mandatory), + length = Snackbar.LENGTH_LONG, + ) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.selectKey.setOnClickListener { - // TODO(msfjarvis): Restore this functionality - // gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) + gpgKeySelectAction.launch(PGPKeyListActivity.newSelectionActivity(requireContext())) } } diff --git a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt index a67d0f32..34cc70a1 100644 --- a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt +++ b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyList.kt @@ -1,6 +1,7 @@ package app.passwordstore.ui.pgp import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -28,12 +29,14 @@ import androidx.compose.ui.unit.dp import app.passwordstore.R import app.passwordstore.crypto.GpgIdentifier import app.passwordstore.ui.compose.theme.APSThemePreview +import app.passwordstore.util.extensions.conditional @Composable fun KeyList( identifiers: List, onItemClick: (identifier: GpgIdentifier) -> Unit, modifier: Modifier = Modifier, + onKeySelected: ((identifier: GpgIdentifier) -> Unit)? = null, ) { if (identifiers.isEmpty()) { Column( @@ -50,7 +53,7 @@ fun KeyList( } else { LazyColumn(modifier = modifier) { items(identifiers) { identifier -> - KeyItem(identifier = identifier, onItemClick = onItemClick) + KeyItem(identifier = identifier, onItemClick = onItemClick, onKeySelected = onKeySelected) } } } @@ -61,6 +64,7 @@ private fun KeyItem( identifier: GpgIdentifier, onItemClick: (identifier: GpgIdentifier) -> Unit, modifier: Modifier = Modifier, + onKeySelected: ((identifier: GpgIdentifier) -> Unit)? = null, ) { var isDeleting by remember { mutableStateOf(false) } DeleteConfirmationDialog( @@ -77,16 +81,21 @@ private fun KeyItem( is GpgIdentifier.UserId -> identifier.email } Row( - modifier = modifier.padding(16.dp).fillMaxWidth(), + modifier = + modifier.padding(16.dp).fillMaxWidth().conditional(onKeySelected != null) { + clickable { onKeySelected?.invoke(identifier) } + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Text(text = label) - IconButton(onClick = { isDeleting = true }) { - Icon( - painter = painterResource(R.drawable.ic_delete_24dp), - stringResource(id = R.string.delete) - ) + if (onKeySelected == null) { + IconButton(onClick = { isDeleting = true }) { + Icon( + painter = painterResource(R.drawable.ic_delete_24dp), + stringResource(id = R.string.delete) + ) + } } } } diff --git a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt index 6916e28b..6a65daaf 100644 --- a/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/pgp/PGPKeyListActivity.kt @@ -1,5 +1,6 @@ package app.passwordstore.ui.pgp +import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity @@ -12,12 +13,10 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable 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.lifecycle.viewmodel.compose.viewModel import app.passwordstore.R import app.passwordstore.ui.APSAppBar import app.passwordstore.ui.compose.theme.APSTheme @@ -39,6 +38,7 @@ class PGPKeyListActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val isSelecting = intent.extras?.getBoolean(EXTRA_KEY_SELECTION) ?: false setContent { val context = LocalContext.current APSTheme(colors = decideColorScheme(context)) { @@ -62,21 +62,33 @@ class PGPKeyListActivity : ComponentActivity() { } } ) { paddingValues -> - PGPKeyList(viewModel = viewModel, modifier = Modifier.padding(paddingValues)) + KeyList( + identifiers = viewModel.keys, + onItemClick = viewModel::deleteKey, + modifier = Modifier.padding(paddingValues), + onKeySelected = + if (isSelecting) { + { identifier -> + val result = Intent() + result.putExtra(EXTRA_SELECTED_KEY, identifier.toString()) + setResult(RESULT_OK, result) + finish() + } + } else null, + ) } } } } -} -@Composable -fun PGPKeyList( - modifier: Modifier = Modifier, - viewModel: PGPKeyListViewModel = viewModel(), -) { - KeyList( - identifiers = viewModel.keys, - onItemClick = viewModel::deleteKey, - modifier = modifier, - ) + companion object { + const val EXTRA_SELECTED_KEY = "SELECTED_KEY" + private const val EXTRA_KEY_SELECTION = "KEY_SELECTION_MODE" + + fun newSelectionActivity(context: Context): Intent { + val intent = Intent(context, PGPKeyListActivity::class.java) + intent.putExtra(EXTRA_KEY_SELECTION, true) + return intent + } + } } diff --git a/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt b/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt index d3e840c7..a780163d 100644 --- a/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt +++ b/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.autofill.AutofillManager import androidx.activity.ComponentActivity import androidx.annotation.RequiresApi +import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.fragment.app.FragmentActivity @@ -153,3 +154,12 @@ fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): Ap @Suppress("DEPRECATION") getApplicationInfo(packageName, flags) } } + +/** Allows conditionally applying the given [modifier] if [isEnabled] is `true`. */ +fun Modifier.conditional(isEnabled: Boolean, modifier: Modifier.() -> Modifier): Modifier { + return if (isEnabled) { + then(modifier()) + } else { + this + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5569929..f3e2dbd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,6 +43,7 @@ Remove %1$s from store. Rename %1$s to %2$s. Move multiple passwords to %1$s. + Initialize GPG IDs in %1$s. Copied to clipboard @@ -318,9 +319,8 @@ There was a conflict when trying to rebase. Your local %1$s branch was pushed to another branch named %2$s\n Use this branch to resolve conflict on your computer The repository is not rebasing, no need to push to another branch - - + Selecting a GPG key is necessary to proceed A file by that name already exists A folder by that name already exists