Bug and crash fixes.

This commit is contained in:
Kevin Gorham 2020-02-12 07:58:41 -05:00
parent 6da700d683
commit b630b9fa78
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
12 changed files with 110 additions and 30 deletions

View File

@ -24,7 +24,7 @@ fun View.disabledIf(isDisabled: Boolean) {
fun View.onClickNavTo(navResId: Int) {
setOnClickListener {
(context as? MainActivity)?.navController?.navigate(navResId)
(context as? MainActivity)?.safeNavigate(navResId)
?: throw IllegalStateException("Cannot navigate from this activity. " +
"Expected MainActivity but found ${context.javaClass.simpleName}")
}

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.ui
import android.Manifest
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@ -17,6 +18,7 @@ import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
@ -34,6 +36,9 @@ import cash.z.ecc.android.feedback.LaunchMetric
import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED
import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.exception.CompactBlockProcessorException
import cash.z.wallet.sdk.ext.twig
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -41,6 +46,7 @@ import javax.inject.Inject
class MainActivity : AppCompatActivity() {
@Inject
lateinit var feedback: Feedback
@ -52,11 +58,14 @@ class MainActivity : AppCompatActivity() {
private val mediaPlayer: MediaPlayer = MediaPlayer()
private var snackbar: Snackbar? = null
private var dialog: Dialog? = null
lateinit var navController: NavController
lateinit var component: MainActivitySubcomponent
lateinit var synchronizerComponent: SynchronizerSubcomponent
var navController: NavController? = null
private val navInitListeners: MutableList<() -> Unit> = mutableListOf()
private val hasCameraPermission
get() = ContextCompat.checkSelfPermission(
this,
@ -118,19 +127,49 @@ class MainActivity : AppCompatActivity() {
private fun initNavigation() {
navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, _, _ ->
navController!!.addOnDestinationChangedListener { _, _, _ ->
// hide the keyboard anytime we change destinations
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
this@MainActivity.window.decorView.rootView.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
for (listener in navInitListeners) {
listener()
}
navInitListeners.clear()
}
fun safeNavigate(@IdRes destination: Int) {
if (navController == null) {
navInitListeners.add {
try {
navController?.navigate(destination)
} catch (t: Throwable) {
twig("WARNING: during callback, did not navigate to destination: R.id.${resources.getResourceEntryName(destination)} due to: $t")
}
}
} else {
try {
navController?.navigate(destination)
} catch (t: Throwable) {
twig("WARNING: did not immediately navigate to destination: R.id.${resources.getResourceEntryName(destination)} due to: $t")
}
}
}
fun startSync(initializer: Initializer) {
synchronizerComponent = ZcashWalletApp.component.synchronizerSubcomponent().create(initializer)
feedback.report(SYNC_START)
synchronizerComponent.synchronizer().start(lifecycleScope)
if (!::synchronizerComponent.isInitialized) {
synchronizerComponent = ZcashWalletApp.component.synchronizerSubcomponent().create(initializer)
feedback.report(SYNC_START)
synchronizerComponent.synchronizer().let { synchronizer ->
synchronizer.onProcessorErrorHandler = ::onProcessorError
synchronizer.start(lifecycleScope)
}
} else {
twig("Ignoring request to start sync because sync has already been started!")
}
}
fun playSound(fileName: String) {
@ -218,6 +257,19 @@ class MainActivity : AppCompatActivity() {
}
}
fun showKeyboard(focusedView: View) {
twig("SHOWING KEYBOARD")
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(focusedView, InputMethodManager.SHOW_FORCED)
}
fun hideKeyboard() {
twig("HIDING KEYBOARD")
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(findViewById<View>(android.R.id.content).windowToken, 0)
}
/**
* @param popUpToInclusive the destination to remove from the stack before opening the camera.
* This only takes effect in the common case where the permission is granted.
@ -250,10 +302,31 @@ class MainActivity : AppCompatActivity() {
}
private fun openCamera(popUpToInclusive: Int? = null) {
navController.navigate(popUpToInclusive ?: R.id.action_global_nav_scan)
navController?.navigate(popUpToInclusive ?: R.id.action_global_nav_scan)
}
private fun onNoCamera() {
showSnackbar("Well, this is awkward. You denied permission for the camera.")
}
private fun onProcessorError(error: Throwable?): Boolean {
when (error) {
is CompactBlockProcessorException.Uninitialized -> {
if (dialog == null)
runOnUiThread {
dialog = MaterialAlertDialogBuilder(this)
.setTitle("Wallet Improperly Initialized")
.setMessage("This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. Please reimport using your backup seed phrase.")
.setCancelable(false)
.setPositiveButton("Exit") { dialog, _ ->
dialog.dismiss()
throw error
}
.show()
}
}
}
feedback.report(error)
return true
}
}

View File

@ -45,7 +45,7 @@ abstract class BaseFragment<T : ViewBinding> : Fragment() {
fun onBackPressNavTo(navResId: Int) {
mainActivity?.onFragmentBackPressed(this) {
mainActivity?.navController?.navigate(navResId)
mainActivity?.safeNavigate(navResId)
}
}
}

View File

@ -13,6 +13,7 @@ import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.isShielded
import cash.z.wallet.sdk.ext.toAbbreviatedAddress
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.*
@ -81,8 +82,8 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
val txId = transaction.rawTransactionId.toTxId()
val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" +
"Transaction: $txId" +
"${if (transaction.toAddress != null) "\nto: ${transaction.toAddress}" else ""}" +
"${if (transaction.memo != null) "\nmemo: ${transaction.toAddress}" else ""}"
"${if (transaction.toAddress != null) "\n\nto: ${transaction.toAddress}" else ""}" +
"${if (transaction.memo != null) "\n\nmemo: \n${String(transaction.memo!!, Charset.forName("UTF-8"))}" else ""}"
MaterialAlertDialogBuilder(itemView.context)
.setMessage(detailsMessage)

View File

@ -21,10 +21,7 @@ import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.SYNCED
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.convertZecToZatoshi
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal
import cash.z.wallet.sdk.ext.twig
import cash.z.wallet.sdk.ext.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@ -65,7 +62,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
// interact with user to create, backup and verify seed
// leads to a call to startSync(), later (after accounts are created from seed)
twig("Seed not found, therefore, launching seed creation flow")
mainActivity?.navController?.navigate(R.id.action_nav_home_to_create_wallet)
mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet)
} else {
twig("Found seed. Re-opening existing wallet")
mainActivity?.startSync(walletSetup.openWallet())
@ -299,7 +296,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
}
private fun onSend() {
mainActivity?.navController?.navigate(R.id.action_nav_home_to_send)
mainActivity?.safeNavigate(R.id.action_nav_home_to_send)
}
private fun onBannerAction(action: BannerAction) {
@ -311,7 +308,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
.setCancelable(true)
.setPositiveButton("View Address") { dialog, _ ->
dialog.dismiss()
mainActivity?.navController?.navigate(R.id.action_nav_home_to_nav_receive)
mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive)
}
.show()
// MaterialAlertDialogBuilder(activity)
@ -324,7 +321,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
// }
// .setNegativeButton("View Address") { dialog, _ ->
// dialog.dismiss()
// mainActivity?.navController?.navigate(R.id.action_nav_home_to_nav_receive)
// mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive)
// }
// .show()
}

View File

@ -5,6 +5,7 @@ import cash.z.wallet.sdk.SdkSynchronizer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.*
import cash.z.wallet.sdk.block.CompactBlockProcessor
import cash.z.wallet.sdk.exception.RustLayerException
import cash.z.wallet.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
import cash.z.wallet.sdk.ext.twig
@ -71,7 +72,11 @@ class HomeViewModel @Inject constructor() : ViewModel() {
}
suspend fun refreshBalance() {
(synchronizer as SdkSynchronizer).refreshBalance()
try {
(synchronizer as SdkSynchronizer).refreshBalance()
} catch (e: RustLayerException.BalanceException) {
twig("Balance refresh failed. This is probably caused by a critical error but we'll give the app a chance to try to recover.")
}
}
data class UiModel( // <- THIS ERROR IS AN IDE BUG WITH PARCELIZE

View File

@ -79,7 +79,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
if (viewModel.isNotValid(qrContent)) image.close() // continue scanning
else {
sendViewModel.toAddress = qrContent
mainActivity?.navController?.navigate(R.id.action_nav_scan_to_nav_send_address)
mainActivity?.safeNavigate(R.id.action_nav_scan_to_nav_send_address)
}
}
}

View File

@ -99,7 +99,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
binding.inputZcashAmount.convertZecToZatoshi()?.let { sendViewModel.zatoshiAmount = it }
sendViewModel.validate(maxZatoshi).onFirstWith(resumedScope) {
if (it == null) {
mainActivity?.navController?.navigate(R.id.action_nav_send_address_to_send_memo)
mainActivity?.safeNavigate(R.id.action_nav_send_address_to_send_memo)
} else {
resumedScope.launch {
binding.textAddressError.text = it

View File

@ -42,6 +42,6 @@ class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
}
private fun onSend() {
mainActivity?.navController?.navigate(R.id.action_nav_send_confirm_to_send_final)
mainActivity?.safeNavigate(R.id.action_nav_send_confirm_to_send_final)
}
}

View File

@ -72,9 +72,8 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
binding.buttonNext.text = "ADD MEMO"
binding.buttonSkip.text = "OMIT MEMO"
} else {
binding.buttonNext.text = "WAIT, GO BACK"
binding.buttonNext.text = "GO BACK"
binding.buttonSkip.text = "PROCEED"
binding.sadTitle.text = binding.sadTitle.text.toString().toColoredSpan(R.color.colorPrimary, "sad")
}
}
}
@ -94,7 +93,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
sendViewModel.memo = binding.inputMemo.text.toString()
onNext()
} else {
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_nav_send_address)
mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_nav_send_address)
}
}
@ -106,6 +105,6 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
}
private fun onNext() {
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_send_confirm)
}
}

View File

@ -69,12 +69,17 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
walletSetup.checkSeed().onEach {
when(it) {
SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> {
mainActivity?.navController?.navigate(R.id.nav_backup)
mainActivity?.safeNavigate(R.id.nav_backup)
}
}
}.launchIn(lifecycleScope)
}
override fun onResume() {
super.onResume()
mainActivity?.hideKeyboard()
}
private fun onSkip(count: Int) {
when (count) {
1 -> {
@ -94,7 +99,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
}
private fun onRestoreWallet() {
mainActivity?.navController?.navigate(R.id.action_nav_landing_to_nav_restore)
mainActivity?.safeNavigate(R.id.action_nav_landing_to_nav_restore)
}
// AKA import wallet
@ -133,7 +138,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
private fun onBackupWallet() {
skipCount = 0
mainActivity?.navController?.navigate(R.id.action_nav_landing_to_nav_backup)
mainActivity?.safeNavigate(R.id.action_nav_landing_to_nav_backup)
}
private fun onEnterWallet() {

View File

@ -1,5 +1,5 @@
<resources>
<string name="app_name">Zcash Wallet</string>
<string name="app_name">zECC Wallet</string>
<string name="receive_address_title">Your Shielded Address</string>