feat: wire up key import functionality
This commit is contained in:
parent
a96f24ac96
commit
c4edf7f0e6
6 changed files with 105 additions and 25 deletions
|
@ -9,7 +9,9 @@ import android.os.Bundle
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) {
|
class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
|
@ -5,21 +5,68 @@
|
||||||
|
|
||||||
package app.passwordstore.ui.onboarding.fragments
|
package app.passwordstore.ui.onboarding.fragments
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
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.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
|
import app.passwordstore.data.repo.PasswordRepository
|
||||||
import app.passwordstore.databinding.FragmentKeySelectionBinding
|
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.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) {
|
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 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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.selectKey.setOnClickListener {
|
binding.selectKey.setOnClickListener {
|
||||||
// TODO(msfjarvis): Restore this functionality
|
gpgKeySelectAction.launch(PGPKeyListActivity.newSelectionActivity(requireContext()))
|
||||||
// gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package app.passwordstore.ui.pgp
|
package app.passwordstore.ui.pgp
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -28,12 +29,14 @@ import androidx.compose.ui.unit.dp
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
import app.passwordstore.crypto.GpgIdentifier
|
import app.passwordstore.crypto.GpgIdentifier
|
||||||
import app.passwordstore.ui.compose.theme.APSThemePreview
|
import app.passwordstore.ui.compose.theme.APSThemePreview
|
||||||
|
import app.passwordstore.util.extensions.conditional
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun KeyList(
|
fun KeyList(
|
||||||
identifiers: List<GpgIdentifier>,
|
identifiers: List<GpgIdentifier>,
|
||||||
onItemClick: (identifier: GpgIdentifier) -> Unit,
|
onItemClick: (identifier: GpgIdentifier) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onKeySelected: ((identifier: GpgIdentifier) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
if (identifiers.isEmpty()) {
|
if (identifiers.isEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
|
@ -50,7 +53,7 @@ fun KeyList(
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(modifier = modifier) {
|
LazyColumn(modifier = modifier) {
|
||||||
items(identifiers) { identifier ->
|
items(identifiers) { identifier ->
|
||||||
KeyItem(identifier = identifier, onItemClick = onItemClick)
|
KeyItem(identifier = identifier, onItemClick = onItemClick, onKeySelected = onKeySelected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +64,7 @@ private fun KeyItem(
|
||||||
identifier: GpgIdentifier,
|
identifier: GpgIdentifier,
|
||||||
onItemClick: (identifier: GpgIdentifier) -> Unit,
|
onItemClick: (identifier: GpgIdentifier) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onKeySelected: ((identifier: GpgIdentifier) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
var isDeleting by remember { mutableStateOf(false) }
|
var isDeleting by remember { mutableStateOf(false) }
|
||||||
DeleteConfirmationDialog(
|
DeleteConfirmationDialog(
|
||||||
|
@ -77,16 +81,21 @@ private fun KeyItem(
|
||||||
is GpgIdentifier.UserId -> identifier.email
|
is GpgIdentifier.UserId -> identifier.email
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.padding(16.dp).fillMaxWidth(),
|
modifier =
|
||||||
|
modifier.padding(16.dp).fillMaxWidth().conditional(onKeySelected != null) {
|
||||||
|
clickable { onKeySelected?.invoke(identifier) }
|
||||||
|
},
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(text = label)
|
Text(text = label)
|
||||||
IconButton(onClick = { isDeleting = true }) {
|
if (onKeySelected == null) {
|
||||||
Icon(
|
IconButton(onClick = { isDeleting = true }) {
|
||||||
painter = painterResource(R.drawable.ic_delete_24dp),
|
Icon(
|
||||||
stringResource(id = R.string.delete)
|
painter = painterResource(R.drawable.ic_delete_24dp),
|
||||||
)
|
stringResource(id = R.string.delete)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.passwordstore.ui.pgp
|
package app.passwordstore.ui.pgp
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
@ -12,12 +13,10 @@ import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
import app.passwordstore.ui.APSAppBar
|
import app.passwordstore.ui.APSAppBar
|
||||||
import app.passwordstore.ui.compose.theme.APSTheme
|
import app.passwordstore.ui.compose.theme.APSTheme
|
||||||
|
@ -39,6 +38,7 @@ class PGPKeyListActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
val isSelecting = intent.extras?.getBoolean(EXTRA_KEY_SELECTION) ?: false
|
||||||
setContent {
|
setContent {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
APSTheme(colors = decideColorScheme(context)) {
|
APSTheme(colors = decideColorScheme(context)) {
|
||||||
|
@ -62,21 +62,33 @@ class PGPKeyListActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { 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
|
companion object {
|
||||||
fun PGPKeyList(
|
const val EXTRA_SELECTED_KEY = "SELECTED_KEY"
|
||||||
modifier: Modifier = Modifier,
|
private const val EXTRA_KEY_SELECTION = "KEY_SELECTION_MODE"
|
||||||
viewModel: PGPKeyListViewModel = viewModel(),
|
|
||||||
) {
|
fun newSelectionActivity(context: Context): Intent {
|
||||||
KeyList(
|
val intent = Intent(context, PGPKeyListActivity::class.java)
|
||||||
identifiers = viewModel.keys,
|
intent.putExtra(EXTRA_KEY_SELECTION, true)
|
||||||
onItemClick = viewModel::deleteKey,
|
return intent
|
||||||
modifier = modifier,
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View
|
||||||
import android.view.autofill.AutofillManager
|
import android.view.autofill.AutofillManager
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
@ -153,3 +154,12 @@ fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): Ap
|
||||||
@Suppress("DEPRECATION") getApplicationInfo(packageName, flags)
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<string name="git_commit_remove_text">Remove %1$s from store.</string>
|
<string name="git_commit_remove_text">Remove %1$s from store.</string>
|
||||||
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
|
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
|
||||||
<string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string>
|
<string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string>
|
||||||
|
<string name="git_commit_gpg_id">Initialize GPG IDs in %1$s.</string>
|
||||||
|
|
||||||
<!-- PGPHandler -->
|
<!-- PGPHandler -->
|
||||||
<string name="clipboard_copied_text">Copied to clipboard</string>
|
<string name="clipboard_copied_text">Copied to clipboard</string>
|
||||||
|
@ -318,9 +319,8 @@
|
||||||
<string name="git_break_out_of_detached_success">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</string>
|
<string name="git_break_out_of_detached_success">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</string>
|
||||||
<string name="git_break_out_of_detached_unneeded">The repository is not rebasing, no need to push to another branch</string>
|
<string name="git_break_out_of_detached_unneeded">The repository is not rebasing, no need to push to another branch</string>
|
||||||
|
|
||||||
<!-- OpenKeychain not installed -->
|
|
||||||
|
|
||||||
<!-- GPG key selection in folder creation -->
|
<!-- GPG key selection in folder creation -->
|
||||||
|
<string name="gpg_key_select_mandatory">Selecting a GPG key is necessary to proceed</string>
|
||||||
<string name="folder_creation_err_file_exists">A file by that name already exists</string>
|
<string name="folder_creation_err_file_exists">A file by that name already exists</string>
|
||||||
<string name="folder_creation_err_folder_exists">A folder by that name already exists</string>
|
<string name="folder_creation_err_folder_exists">A folder by that name already exists</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue