Add tentative workaround for dialog crashes and refactor Git-related code (#1100)
* Ensure we're creating dialogs on the main thread Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Remove unused operation type Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Refactor launchGitOperation to use an enum Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
eef809760c
commit
a34f749e9a
7 changed files with 58 additions and 63 deletions
|
@ -17,7 +17,6 @@ import com.github.michaelbull.result.onFailure
|
||||||
import com.github.michaelbull.result.runCatching
|
import com.github.michaelbull.result.runCatching
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.zeapo.pwdstore.databinding.ActivityOnboardingBinding
|
import com.zeapo.pwdstore.databinding.ActivityOnboardingBinding
|
||||||
import com.zeapo.pwdstore.git.BaseGitActivity
|
|
||||||
import com.zeapo.pwdstore.git.GitServerConfigActivity
|
import com.zeapo.pwdstore.git.GitServerConfigActivity
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
|
@ -92,9 +91,7 @@ class OnboardingActivity : AppCompatActivity() {
|
||||||
*/
|
*/
|
||||||
private fun cloneToHiddenDir() {
|
private fun cloneToHiddenDir() {
|
||||||
settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, false) }
|
settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, false) }
|
||||||
cloneAction.launch(Intent(this, GitServerConfigActivity::class.java).apply {
|
cloneAction.launch(GitServerConfigActivity.createCloneIntent(this))
|
||||||
putExtra(BaseGitActivity.REQUEST_ARG_OP, BaseGitActivity.REQUEST_CLONE)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package com.zeapo.pwdstore
|
package com.zeapo.pwdstore
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -90,9 +89,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
} else if (!PasswordRepository.isGitRepo()) {
|
} else if (!PasswordRepository.isGitRepo()) {
|
||||||
Snackbar.make(binding.root, getString(R.string.clone_git_repo), Snackbar.LENGTH_INDEFINITE)
|
Snackbar.make(binding.root, getString(R.string.clone_git_repo), Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(R.string.clone_button) {
|
.setAction(R.string.clone_button) {
|
||||||
val intent = Intent(context, GitServerConfigActivity::class.java)
|
swipeResult.launch(GitServerConfigActivity.createCloneIntent(requireContext()))
|
||||||
intent.putExtra(BaseGitActivity.REQUEST_ARG_OP, BaseGitActivity.REQUEST_CLONE)
|
|
||||||
swipeResult.launch(intent)
|
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
binding.swipeRefresher.isRefreshing = false
|
binding.swipeRefresher.isRefreshing = false
|
||||||
|
@ -100,8 +97,8 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
// When authentication is set to AuthMode.None then the only git operation we can
|
// When authentication is set to AuthMode.None then the only git operation we can
|
||||||
// run is a pull, so automatically fallback to that.
|
// run is a pull, so automatically fallback to that.
|
||||||
val operationId = when (GitSettings.authMode) {
|
val operationId = when (GitSettings.authMode) {
|
||||||
AuthMode.None -> BaseGitActivity.REQUEST_PULL
|
AuthMode.None -> BaseGitActivity.GitOp.PULL
|
||||||
else -> BaseGitActivity.REQUEST_SYNC
|
else -> BaseGitActivity.GitOp.SYNC
|
||||||
}
|
}
|
||||||
requireStore().apply {
|
requireStore().apply {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
|
@ -272,7 +272,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
initBefore.show()
|
initBefore.show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
runGitOperation(REQUEST_PUSH)
|
runGitOperation(GitOp.PUSH)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.git_pull -> {
|
R.id.git_pull -> {
|
||||||
|
@ -280,7 +280,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
initBefore.show()
|
initBefore.show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
runGitOperation(REQUEST_PULL)
|
runGitOperation(GitOp.PULL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.git_sync -> {
|
R.id.git_sync -> {
|
||||||
|
@ -288,7 +288,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
initBefore.show()
|
initBefore.show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
runGitOperation(REQUEST_SYNC)
|
runGitOperation(GitOp.SYNC)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.refresh -> {
|
R.id.refresh -> {
|
||||||
|
@ -312,10 +312,10 @@ class PasswordStore : BaseGitActivity() {
|
||||||
searchItem.collapseActionView()
|
searchItem.collapseActionView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runGitOperation(operation: Int) = lifecycleScope.launch {
|
private fun runGitOperation(operation: GitOp) = lifecycleScope.launch {
|
||||||
launchGitOperation(operation).fold(
|
launchGitOperation(operation).fold(
|
||||||
success = { refreshPasswordList() },
|
success = { refreshPasswordList() },
|
||||||
failure = ::promptOnErrorHandler
|
failure = { promptOnErrorHandler(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,7 +520,6 @@ class PasswordStore : BaseGitActivity() {
|
||||||
val intent = Intent(this, SelectFolderActivity::class.java)
|
val intent = Intent(this, SelectFolderActivity::class.java)
|
||||||
val fileLocations = values.map { it.file.absolutePath }.toTypedArray()
|
val fileLocations = values.map { it.file.absolutePath }.toTypedArray()
|
||||||
intent.putExtra("Files", fileLocations)
|
intent.putExtra("Files", fileLocations)
|
||||||
intent.putExtra(REQUEST_ARG_OP, "SELECTFOLDER")
|
|
||||||
registerForActivityResult(StartActivityForResult()) { result ->
|
registerForActivityResult(StartActivityForResult()) { result ->
|
||||||
val intentData = result.data ?: return@registerForActivityResult
|
val intentData = result.data ?: return@registerForActivityResult
|
||||||
val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files"))
|
val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files"))
|
||||||
|
|
|
@ -6,9 +6,7 @@ package com.zeapo.pwdstore.git
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.github.ajalt.timberkt.Timber.tag
|
|
||||||
import com.github.ajalt.timberkt.d
|
import com.github.ajalt.timberkt.d
|
||||||
import com.github.ajalt.timberkt.e
|
|
||||||
import com.github.michaelbull.result.Err
|
import com.github.michaelbull.result.Err
|
||||||
import com.github.michaelbull.result.Result
|
import com.github.michaelbull.result.Result
|
||||||
import com.github.michaelbull.result.andThen
|
import com.github.michaelbull.result.andThen
|
||||||
|
@ -18,7 +16,6 @@ import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.git.config.GitSettings
|
import com.zeapo.pwdstore.git.config.GitSettings
|
||||||
import com.zeapo.pwdstore.git.operation.BreakOutOfDetached
|
import com.zeapo.pwdstore.git.operation.BreakOutOfDetached
|
||||||
import com.zeapo.pwdstore.git.operation.CloneOperation
|
import com.zeapo.pwdstore.git.operation.CloneOperation
|
||||||
import com.zeapo.pwdstore.git.operation.GitOperation
|
|
||||||
import com.zeapo.pwdstore.git.operation.PullOperation
|
import com.zeapo.pwdstore.git.operation.PullOperation
|
||||||
import com.zeapo.pwdstore.git.operation.PushOperation
|
import com.zeapo.pwdstore.git.operation.PushOperation
|
||||||
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
||||||
|
@ -26,6 +23,8 @@ import com.zeapo.pwdstore.git.operation.SyncOperation
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import net.schmizz.sshj.common.DisconnectReason
|
import net.schmizz.sshj.common.DisconnectReason
|
||||||
import net.schmizz.sshj.common.SSHException
|
import net.schmizz.sshj.common.SSHException
|
||||||
import net.schmizz.sshj.userauth.UserAuthException
|
import net.schmizz.sshj.userauth.UserAuthException
|
||||||
|
@ -36,30 +35,38 @@ import net.schmizz.sshj.userauth.UserAuthException
|
||||||
*/
|
*/
|
||||||
abstract class BaseGitActivity : AppCompatActivity() {
|
abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of possible Git operations than can be run through [launchGitOperation].
|
||||||
|
*/
|
||||||
|
enum class GitOp {
|
||||||
|
BREAK_OUT_OF_DETACHED,
|
||||||
|
CLONE,
|
||||||
|
PULL,
|
||||||
|
PUSH,
|
||||||
|
RESET,
|
||||||
|
SYNC,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to launch the requested Git operation.
|
* Attempt to launch the requested Git operation.
|
||||||
* @param operation The type of git operation to launch
|
* @param operation The type of git operation to launch
|
||||||
*/
|
*/
|
||||||
suspend fun launchGitOperation(operation: Int): Result<Unit, Throwable> {
|
suspend fun launchGitOperation(operation: GitOp): Result<Unit, Throwable> {
|
||||||
if (GitSettings.url == null) {
|
if (GitSettings.url == null) {
|
||||||
return Err(IllegalStateException("Git url is not set!"))
|
return Err(IllegalStateException("Git url is not set!"))
|
||||||
}
|
}
|
||||||
if (operation == REQUEST_SYNC && !GitSettings.useMultiplexing) {
|
if (operation == GitOp.SYNC && !GitSettings.useMultiplexing) {
|
||||||
// If the server does not support multiple SSH channels per connection, we cannot run
|
// If the server does not support multiple SSH channels per connection, we cannot run
|
||||||
// a sync operation without reconnecting and thus break sync into its two parts.
|
// a sync operation without reconnecting and thus break sync into its two parts.
|
||||||
return launchGitOperation(REQUEST_PULL).andThen { launchGitOperation(REQUEST_PUSH) }
|
return launchGitOperation(GitOp.PULL).andThen { launchGitOperation(GitOp.PUSH) }
|
||||||
}
|
}
|
||||||
val op = when (operation) {
|
val op = when (operation) {
|
||||||
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(this, GitSettings.url!!)
|
GitOp.CLONE -> CloneOperation(this, GitSettings.url!!)
|
||||||
REQUEST_PULL -> PullOperation(this)
|
GitOp.PULL -> PullOperation(this)
|
||||||
REQUEST_PUSH -> PushOperation(this)
|
GitOp.PUSH -> PushOperation(this)
|
||||||
REQUEST_SYNC -> SyncOperation(this)
|
GitOp.SYNC -> SyncOperation(this)
|
||||||
BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(this)
|
GitOp.BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(this)
|
||||||
REQUEST_RESET -> ResetToRemoteOperation(this)
|
GitOp.RESET -> ResetToRemoteOperation(this)
|
||||||
else -> {
|
|
||||||
tag(TAG).e { "Operation not recognized : $operation" }
|
|
||||||
return Err(IllegalArgumentException("$operation is not a valid Git operation"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return op.executeAfterAuthentication(GitSettings.authMode).mapError { throwable ->
|
return op.executeAfterAuthentication(GitSettings.authMode).mapError { throwable ->
|
||||||
val err = rootCauseException(throwable)
|
val err = rootCauseException(throwable)
|
||||||
|
@ -76,7 +83,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun promptOnErrorHandler(err: Throwable, onPromptDone: () -> Unit = {}) {
|
suspend fun promptOnErrorHandler(err: Throwable, onPromptDone: () -> Unit = {}) {
|
||||||
val error = rootCauseException(err)
|
val error = rootCauseException(err)
|
||||||
if (!isExplicitlyUserInitiatedError(error)) {
|
if (!isExplicitlyUserInitiatedError(error)) {
|
||||||
getEncryptedPrefs("git_operation").edit {
|
getEncryptedPrefs("git_operation").edit {
|
||||||
|
@ -84,14 +91,16 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) }
|
sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) }
|
||||||
d(error)
|
d(error)
|
||||||
MaterialAlertDialogBuilder(this).run {
|
withContext(Dispatchers.Main) {
|
||||||
setTitle(resources.getString(R.string.jgit_error_dialog_title))
|
MaterialAlertDialogBuilder(this@BaseGitActivity).run {
|
||||||
setMessage(ErrorMessages[error])
|
setTitle(resources.getString(R.string.jgit_error_dialog_title))
|
||||||
setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> }
|
setMessage(ErrorMessages[error])
|
||||||
setOnDismissListener {
|
setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> }
|
||||||
onPromptDone()
|
setOnDismissListener {
|
||||||
|
onPromptDone()
|
||||||
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
show()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onPromptDone()
|
onPromptDone()
|
||||||
|
@ -130,16 +139,4 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
return rootCause
|
return rootCause
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val REQUEST_ARG_OP = "OPERATION"
|
|
||||||
const val REQUEST_PULL = 101
|
|
||||||
const val REQUEST_PUSH = 102
|
|
||||||
const val REQUEST_CLONE = 103
|
|
||||||
const val REQUEST_SYNC = 104
|
|
||||||
const val BREAK_OUT_OF_DETACHED = 105
|
|
||||||
const val REQUEST_RESET = 106
|
|
||||||
const val TAG = "AbstractGitActivity"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ class GitConfigActivity : BaseGitActivity() {
|
||||||
}
|
}
|
||||||
binding.gitAbortRebase.setOnClickListener {
|
binding.gitAbortRebase.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
launchGitOperation(BREAK_OUT_OF_DETACHED).fold(
|
launchGitOperation(GitOp.BREAK_OUT_OF_DETACHED).fold(
|
||||||
success = {
|
success = {
|
||||||
MaterialAlertDialogBuilder(this@GitConfigActivity).run {
|
MaterialAlertDialogBuilder(this@GitConfigActivity).run {
|
||||||
setTitle(resources.getString(R.string.git_abort_and_push_title))
|
setTitle(resources.getString(R.string.git_abort_and_push_title))
|
||||||
|
@ -115,7 +115,7 @@ class GitConfigActivity : BaseGitActivity() {
|
||||||
}
|
}
|
||||||
binding.gitResetToRemote.setOnClickListener {
|
binding.gitResetToRemote.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
launchGitOperation(REQUEST_RESET).fold(
|
launchGitOperation(GitOp.RESET).fold(
|
||||||
success = ::finishOnSuccessHandler,
|
success = ::finishOnSuccessHandler,
|
||||||
failure = { err ->
|
failure = { err ->
|
||||||
promptOnErrorHandler(err) {
|
promptOnErrorHandler(err) {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
*/
|
*/
|
||||||
package com.zeapo.pwdstore.git
|
package com.zeapo.pwdstore.git
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -42,7 +44,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val isClone = intent?.extras?.getInt(REQUEST_ARG_OP) ?: -1 == REQUEST_CLONE
|
val isClone = intent?.extras?.getBoolean("cloning") ?: false
|
||||||
if (isClone) {
|
if (isClone) {
|
||||||
binding.saveButton.text = getString(R.string.clone_button)
|
binding.saveButton.text = getString(R.string.clone_button)
|
||||||
}
|
}
|
||||||
|
@ -151,7 +153,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
localDir.deleteRecursively()
|
localDir.deleteRecursively()
|
||||||
}
|
}
|
||||||
snackbar.dismiss()
|
snackbar.dismiss()
|
||||||
launchGitOperation(REQUEST_CLONE).fold(
|
launchGitOperation(GitOp.CLONE).fold(
|
||||||
success = {
|
success = {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
|
@ -185,14 +187,22 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
launchGitOperation(REQUEST_CLONE).fold(
|
launchGitOperation(GitOp.CLONE).fold(
|
||||||
success = {
|
success = {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
failure = ::promptOnErrorHandler,
|
failure = { promptOnErrorHandler(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createCloneIntent(context: Context): Intent {
|
||||||
|
return Intent(context, GitServerConfigActivity::class.java).apply {
|
||||||
|
putExtra("cloning", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,9 +202,4 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
sshSessionFactory?.close()
|
sshSessionFactory?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val GET_SSH_KEY_FROM_CLONE = 201
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue