Error handling
This commit is contained in:
parent
3eadde6ddd
commit
8f526602ce
|
@ -1,38 +0,0 @@
|
||||||
package co.electriccoin.zcash.ui.design.component
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
|
||||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ZashiModal(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable BoxScope.() -> Unit,
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
modifier = modifier,
|
|
||||||
shape = RoundedCornerShape(ZashiDimensions.Radius.radius2xl),
|
|
||||||
border = BorderStroke(1.dp, ZashiColors.Modals.surfaceStroke),
|
|
||||||
color = ZashiColors.Modals.surfacePrimary
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(ZashiDimensions.Spacing.spacing3xl)
|
|
||||||
.wrapContentSize()
|
|
||||||
.animateContentSize()
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||||
|
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||||
|
import co.electriccoin.zcash.ui.design.util.getValue
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ZashiScreenDialog(
|
||||||
|
state: DialogState?,
|
||||||
|
properties: DialogProperties = DialogProperties()
|
||||||
|
) {
|
||||||
|
val parent = LocalView.current.parent
|
||||||
|
SideEffect {
|
||||||
|
(parent as? DialogWindowProvider)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||||
|
(parent as? DialogWindowProvider)?.window?.setDimAmount(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
state?.let {
|
||||||
|
Dialog(
|
||||||
|
positive = state.positive,
|
||||||
|
negative = state.negative,
|
||||||
|
onDismissRequest = state.onDismissRequest,
|
||||||
|
title = state.title,
|
||||||
|
message = state.message,
|
||||||
|
properties = properties,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
positive: ButtonState,
|
||||||
|
negative: ButtonState,
|
||||||
|
onDismissRequest: (() -> Unit),
|
||||||
|
title: StringResource,
|
||||||
|
message: StringResource,
|
||||||
|
properties: DialogProperties = DialogProperties()
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
ZashiButton(state = positive)
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
ZashiButton(state = negative, colors = ZashiButtonDefaults.secondaryColors())
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title.getValue(),
|
||||||
|
color = ZashiColors.Text.textPrimary,
|
||||||
|
style = ZashiTypography.textXl,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = message.getValue(),
|
||||||
|
color = ZashiColors.Text.textTertiary,
|
||||||
|
style = ZashiTypography.textMd
|
||||||
|
)
|
||||||
|
},
|
||||||
|
properties = properties,
|
||||||
|
containerColor = ZashiColors.Surfaces.bgPrimary,
|
||||||
|
titleContentColor = ZashiColors.Text.textPrimary,
|
||||||
|
textContentColor = ZashiColors.Text.textPrimary,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class DialogState(
|
||||||
|
val positive: ButtonState,
|
||||||
|
val negative: ButtonState,
|
||||||
|
val onDismissRequest: (() -> Unit),
|
||||||
|
val title: StringResource,
|
||||||
|
val message: StringResource,
|
||||||
|
)
|
|
@ -49,6 +49,7 @@ android {
|
||||||
"src/main/res/ui/connect_keystone",
|
"src/main/res/ui/connect_keystone",
|
||||||
"src/main/res/ui/delete_wallet",
|
"src/main/res/ui/delete_wallet",
|
||||||
"src/main/res/ui/export_data",
|
"src/main/res/ui/export_data",
|
||||||
|
"src/main/res/ui/error",
|
||||||
"src/main/res/ui/home",
|
"src/main/res/ui/home",
|
||||||
"src/main/res/ui/choose_server",
|
"src/main/res/ui/choose_server",
|
||||||
"src/main/res/ui/integrations",
|
"src/main/res/ui/integrations",
|
||||||
|
|
|
@ -44,6 +44,7 @@ import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseC
|
||||||
import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase
|
import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.NavigateToWalletBackupUseCase
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToWalletBackupUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
|
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
|
||||||
|
@ -190,4 +191,5 @@ val useCaseModule =
|
||||||
factoryOf(::RemindWalletBackupLaterUseCase)
|
factoryOf(::RemindWalletBackupLaterUseCase)
|
||||||
factoryOf(::RemindShieldFundsLaterUseCase)
|
factoryOf(::RemindShieldFundsLaterUseCase)
|
||||||
singleOf(::ShieldFundsUseCase)
|
singleOf(::ShieldFundsUseCase)
|
||||||
|
singleOf(::NavigateToErrorUseCase)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import co.electriccoin.zcash.ui.screen.balances.BalanceViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel
|
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel
|
import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel
|
import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.ErrorViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptInViewModel
|
import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptInViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.exchangerate.settings.ExchangeRateSettingsViewModel
|
import co.electriccoin.zcash.ui.screen.exchangerate.settings.ExchangeRateSettingsViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel
|
import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel
|
||||||
|
@ -150,4 +151,5 @@ val viewModelModule =
|
||||||
viewModelOf(::ExchangeRateOptInViewModel)
|
viewModelOf(::ExchangeRateOptInViewModel)
|
||||||
viewModelOf(::ExchangeRateSettingsViewModel)
|
viewModelOf(::ExchangeRateSettingsViewModel)
|
||||||
viewModelOf(::WalletBackupDetailViewModel)
|
viewModelOf(::WalletBackupDetailViewModel)
|
||||||
|
viewModelOf(::ErrorViewModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,10 @@ import co.electriccoin.zcash.ui.screen.contact.WrapAddContact
|
||||||
import co.electriccoin.zcash.ui.screen.contact.WrapUpdateContact
|
import co.electriccoin.zcash.ui.screen.contact.WrapUpdateContact
|
||||||
import co.electriccoin.zcash.ui.screen.deletewallet.WrapDeleteWallet
|
import co.electriccoin.zcash.ui.screen.deletewallet.WrapDeleteWallet
|
||||||
import co.electriccoin.zcash.ui.screen.disconnected.WrapDisconnected
|
import co.electriccoin.zcash.ui.screen.disconnected.WrapDisconnected
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.AndroidErrorBottomSheet
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.AndroidErrorDialog
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.ErrorBottomSheet
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.ErrorDialog
|
||||||
import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOptIn
|
import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOptIn
|
||||||
import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptIn
|
import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptIn
|
||||||
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidExchangeRateSettings
|
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidExchangeRateSettings
|
||||||
|
@ -471,6 +475,24 @@ internal fun MainActivity.Navigation() {
|
||||||
) {
|
) {
|
||||||
AndroidWalletUpdatingInfo()
|
AndroidWalletUpdatingInfo()
|
||||||
}
|
}
|
||||||
|
dialog<ErrorDialog>(
|
||||||
|
dialogProperties =
|
||||||
|
DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
dismissOnClickOutside = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AndroidErrorDialog()
|
||||||
|
}
|
||||||
|
dialog<ErrorBottomSheet>(
|
||||||
|
dialogProperties =
|
||||||
|
DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
dismissOnClickOutside = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AndroidErrorBottomSheet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ sealed interface SubmitResult {
|
||||||
|
|
||||||
data class MultipleTrxFailure(
|
data class MultipleTrxFailure(
|
||||||
val results: List<TransactionSubmitResult>
|
val results: List<TransactionSubmitResult>
|
||||||
) : SubmitResult
|
) : Failure
|
||||||
|
|
||||||
sealed interface SimpleTrxFailure : SubmitResult {
|
sealed interface SimpleTrxFailure : Failure {
|
||||||
fun toErrorMessage(): String
|
fun toErrorMessage(): String
|
||||||
|
|
||||||
fun toErrorStacktrace(): String
|
fun toErrorStacktrace(): String
|
||||||
|
@ -45,4 +45,6 @@ sealed interface SubmitResult {
|
||||||
override fun toErrorStacktrace(): String = error.stackTraceToString()
|
override fun toErrorStacktrace(): String = error.stackTraceToString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed interface Failure : SubmitResult
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package co.electriccoin.zcash.ui.common.usecase
|
||||||
|
|
||||||
|
import co.electriccoin.zcash.ui.NavigationRouter
|
||||||
|
import co.electriccoin.zcash.ui.common.model.SubmitResult
|
||||||
|
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.ErrorBottomSheet
|
||||||
|
import co.electriccoin.zcash.ui.screen.error.ErrorDialog
|
||||||
|
|
||||||
|
class NavigateToErrorUseCase(
|
||||||
|
private val navigationRouter: NavigationRouter,
|
||||||
|
) {
|
||||||
|
private var args: ErrorArgs? = null
|
||||||
|
|
||||||
|
operator fun invoke(args: ErrorArgs) {
|
||||||
|
this.args = args
|
||||||
|
when (args) {
|
||||||
|
is ErrorArgs.ShieldingError -> navigationRouter.forward(ErrorDialog)
|
||||||
|
is ErrorArgs.SyncError -> navigationRouter.forward(ErrorBottomSheet)
|
||||||
|
is ErrorArgs.General -> navigationRouter.forward(ErrorDialog)
|
||||||
|
is ErrorArgs.ShieldingGeneralError -> navigationRouter.forward(ErrorDialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requireCurrentArgs() = args as ErrorArgs
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
args = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface ErrorArgs {
|
||||||
|
data class SyncError(val synchronizerError: SynchronizerError) : ErrorArgs
|
||||||
|
data class ShieldingError(val error: SubmitResult.Failure): ErrorArgs
|
||||||
|
data class ShieldingGeneralError(val exception: Exception): ErrorArgs
|
||||||
|
data class General(val exception: Exception): ErrorArgs
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import android.content.Intent
|
||||||
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.model.SubmitResult
|
import co.electriccoin.zcash.ui.common.model.SubmitResult
|
||||||
|
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
|
||||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||||
import co.electriccoin.zcash.ui.design.util.getString
|
import co.electriccoin.zcash.ui.design.util.getString
|
||||||
import co.electriccoin.zcash.ui.screen.support.model.SupportInfoType
|
import co.electriccoin.zcash.ui.screen.support.model.SupportInfoType
|
||||||
|
@ -28,11 +29,47 @@ class SendEmailUseCase(
|
||||||
).apply {
|
).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
}
|
}
|
||||||
|
runCatching { context.startActivity(intent) }
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend operator fun invoke(submitResult: SubmitResult) {
|
suspend operator fun invoke(exception: Exception) {
|
||||||
|
val fullMessage =
|
||||||
|
EmailUtil.formatMessage(
|
||||||
|
body = exception.stackTraceToString(),
|
||||||
|
supportInfo = getSupport().toSupportString(SupportInfoType.entries.toSet())
|
||||||
|
)
|
||||||
|
val mailIntent =
|
||||||
|
EmailUtil
|
||||||
|
.newMailActivityIntent(
|
||||||
|
recipientAddress = context.getString(R.string.support_email_address),
|
||||||
|
messageSubject = context.getString(R.string.app_name),
|
||||||
|
messageBody = fullMessage
|
||||||
|
).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
runCatching { context.startActivity(mailIntent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend operator fun invoke(synchronizerError: SynchronizerError) {
|
||||||
|
val fullMessage =
|
||||||
|
EmailUtil.formatMessage(
|
||||||
|
body = synchronizerError.getStackTrace(null),
|
||||||
|
supportInfo = getSupport().toSupportString(SupportInfoType.entries.toSet())
|
||||||
|
)
|
||||||
|
val mailIntent =
|
||||||
|
EmailUtil
|
||||||
|
.newMailActivityIntent(
|
||||||
|
recipientAddress = context.getString(R.string.support_email_address),
|
||||||
|
messageSubject = context.getString(R.string.app_name),
|
||||||
|
messageBody = fullMessage
|
||||||
|
).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
runCatching { context.startActivity(mailIntent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(submitResult: SubmitResult.Failure) {
|
||||||
val fullMessage =
|
val fullMessage =
|
||||||
when (submitResult) {
|
when (submitResult) {
|
||||||
is SubmitResult.SimpleTrxFailure -> {
|
is SubmitResult.SimpleTrxFailure -> {
|
||||||
|
@ -41,6 +78,7 @@ class SendEmailUseCase(
|
||||||
supportInfo = submitResult.toErrorStacktrace()
|
supportInfo = submitResult.toErrorStacktrace()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is SubmitResult.MultipleTrxFailure -> {
|
is SubmitResult.MultipleTrxFailure -> {
|
||||||
EmailUtil.formatMessage(
|
EmailUtil.formatMessage(
|
||||||
prefix = context.getString(R.string.send_confirmation_multiple_report_text),
|
prefix = context.getString(R.string.send_confirmation_multiple_report_text),
|
||||||
|
@ -48,9 +86,6 @@ class SendEmailUseCase(
|
||||||
suffix = submitResult.results.toSupportString(context)
|
suffix = submitResult.results.toSupportString(context)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val mailIntent =
|
val mailIntent =
|
||||||
|
|
|
@ -20,7 +20,8 @@ class ShieldFundsUseCase(
|
||||||
private val keystoneProposalRepository: KeystoneProposalRepository,
|
private val keystoneProposalRepository: KeystoneProposalRepository,
|
||||||
private val zashiProposalRepository: ZashiProposalRepository,
|
private val zashiProposalRepository: ZashiProposalRepository,
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
private val accountDataSource: AccountDataSource
|
private val accountDataSource: AccountDataSource,
|
||||||
|
private val navigateToError: NavigateToErrorUseCase,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ class ShieldFundsUseCase(
|
||||||
is KeystoneAccount -> {
|
is KeystoneAccount -> {
|
||||||
createKeystoneShieldProposal()
|
createKeystoneShieldProposal()
|
||||||
}
|
}
|
||||||
|
|
||||||
is ZashiAccount -> {
|
is ZashiAccount -> {
|
||||||
if (navigateBackAfterSuccess) {
|
if (navigateBackAfterSuccess) {
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
|
@ -49,31 +51,11 @@ class ShieldFundsUseCase(
|
||||||
.first()
|
.first()
|
||||||
.submitResult
|
.submitResult
|
||||||
|
|
||||||
when (result) {
|
if (result is SubmitResult.Failure) {
|
||||||
is SubmitResult.Success -> {
|
navigateToError(ErrorArgs.ShieldingError(result))
|
||||||
// do nothing
|
|
||||||
// TODO messages
|
|
||||||
}
|
|
||||||
|
|
||||||
is SubmitResult.SimpleTrxFailure.SimpleTrxFailureGrpc -> {
|
|
||||||
// showShieldingError(ShieldState.FailedGrpc)
|
|
||||||
// TODO messages
|
|
||||||
}
|
|
||||||
|
|
||||||
is SubmitResult.SimpleTrxFailure -> {
|
|
||||||
// showShieldingError(
|
|
||||||
// ShieldState.Failed(
|
|
||||||
// error = result.toErrorMessage(),
|
|
||||||
// stackTrace = result.toErrorStacktrace()
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// TODO messages
|
|
||||||
}
|
|
||||||
|
|
||||||
is SubmitResult.MultipleTrxFailure -> {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
navigateToError(ErrorArgs.ShieldingGeneralError(e))
|
||||||
} finally {
|
} finally {
|
||||||
zashiProposalRepository.clear()
|
zashiProposalRepository.clear()
|
||||||
}
|
}
|
||||||
|
@ -86,16 +68,7 @@ class ShieldFundsUseCase(
|
||||||
navigationRouter.forward(SignKeystoneTransaction)
|
navigationRouter.forward(SignKeystoneTransaction)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
keystoneProposalRepository.clear()
|
keystoneProposalRepository.clear()
|
||||||
// TODO messages
|
navigateToError(ErrorArgs.ShieldingGeneralError(e))
|
||||||
// showShieldingError(
|
|
||||||
// ShieldState.Failed(
|
|
||||||
// error =
|
|
||||||
// context.getString(
|
|
||||||
// R.string.balances_shielding_dialog_error_text_below_threshold
|
|
||||||
// ),
|
|
||||||
// stackTrace = ""
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AndroidErrorBottomSheet() {
|
||||||
|
val useCase = koinInject<NavigateToErrorUseCase>()
|
||||||
|
val vm = koinViewModel<ErrorViewModel> { parametersOf(useCase.requireCurrentArgs()) }
|
||||||
|
val state by vm.state.collectAsStateWithLifecycle()
|
||||||
|
BottomSheetErrorView(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object ErrorBottomSheet
|
|
@ -0,0 +1,21 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AndroidErrorDialog() {
|
||||||
|
val useCase = koinInject<NavigateToErrorUseCase>()
|
||||||
|
val vm = koinViewModel<ErrorViewModel> { parametersOf(useCase.requireCurrentArgs()) }
|
||||||
|
val state by vm.state.collectAsStateWithLifecycle()
|
||||||
|
DialogView(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object ErrorDialog
|
|
@ -0,0 +1,94 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.SheetState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.Spacer
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiScreenModalBottomSheet
|
||||||
|
import co.electriccoin.zcash.ui.design.component.rememberScreenModalBottomSheetState
|
||||||
|
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||||
|
import co.electriccoin.zcash.ui.design.util.getValue
|
||||||
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BottomSheetErrorView(
|
||||||
|
state: ErrorState?,
|
||||||
|
sheetState: SheetState = rememberScreenModalBottomSheetState(),
|
||||||
|
) {
|
||||||
|
ZashiScreenModalBottomSheet(
|
||||||
|
state = state,
|
||||||
|
sheetState = sheetState,
|
||||||
|
content = {
|
||||||
|
BottomSheetContent(it, modifier = Modifier.weight(1f, false))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BottomSheetContent(state: ErrorState, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = state.title.getValue(),
|
||||||
|
color = ZashiColors.Text.textPrimary,
|
||||||
|
style = ZashiTypography.textXl,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
Spacer(12.dp)
|
||||||
|
Text(
|
||||||
|
text = state.message.getValue(),
|
||||||
|
color = ZashiColors.Text.textTertiary,
|
||||||
|
style = ZashiTypography.textMd
|
||||||
|
)
|
||||||
|
Spacer(32.dp)
|
||||||
|
ZashiButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
state = state.negative,
|
||||||
|
colors = ZashiButtonDefaults.secondaryColors()
|
||||||
|
)
|
||||||
|
Spacer(8.dp)
|
||||||
|
ZashiButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
state = state.positive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@PreviewScreens
|
||||||
|
@Composable
|
||||||
|
private fun Preview() = ZcashTheme {
|
||||||
|
BottomSheetErrorView(
|
||||||
|
state = ErrorState(
|
||||||
|
title = stringRes("Error"),
|
||||||
|
message = stringRes("Something went wrong"),
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes("Positive")
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes("Negative")
|
||||||
|
),
|
||||||
|
onBack = {}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.DialogState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiScreenDialog
|
||||||
|
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DialogView(state: ErrorState?) {
|
||||||
|
ZashiScreenDialog(
|
||||||
|
state = state?.let {
|
||||||
|
DialogState(
|
||||||
|
title = it.title,
|
||||||
|
message = it.message,
|
||||||
|
positive = it.positive,
|
||||||
|
negative = it.negative,
|
||||||
|
onDismissRequest = it.onBack
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewScreens
|
||||||
|
@Composable
|
||||||
|
private fun Preview() = ZcashTheme {
|
||||||
|
DialogView(
|
||||||
|
state = ErrorState(
|
||||||
|
title = stringRes("Error"),
|
||||||
|
message = stringRes("Something went wrong"),
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes("Positive")
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes("Negative")
|
||||||
|
),
|
||||||
|
onBack = {}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
|
||||||
|
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||||
|
|
||||||
|
data class ErrorState(
|
||||||
|
val title: StringResource,
|
||||||
|
val message: StringResource,
|
||||||
|
val positive: ButtonState,
|
||||||
|
val negative: ButtonState,
|
||||||
|
override val onBack: () -> Unit
|
||||||
|
): ModalBottomSheetState
|
|
@ -0,0 +1,128 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.error
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import co.electriccoin.zcash.ui.NavigationRouter
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.common.model.SubmitResult
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.ErrorArgs
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.SendEmailUseCase
|
||||||
|
import co.electriccoin.zcash.ui.common.viewmodel.STACKTRACE_LIMIT
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ErrorViewModel(
|
||||||
|
args: ErrorArgs,
|
||||||
|
private val navigateToErrorBottom: NavigateToErrorUseCase,
|
||||||
|
private val navigationRouter: NavigationRouter,
|
||||||
|
private val sendEmailUseCase: SendEmailUseCase,
|
||||||
|
) : ViewModel() {
|
||||||
|
val state: StateFlow<ErrorState> = MutableStateFlow(createState(args)).asStateFlow()
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
navigateToErrorBottom.clear()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBack() = navigationRouter.back()
|
||||||
|
|
||||||
|
private fun createState(args: ErrorArgs): ErrorState = when (args) {
|
||||||
|
is ErrorArgs.SyncError -> createSyncErrorState(args)
|
||||||
|
is ErrorArgs.ShieldingError -> createShieldingErrorState(args)
|
||||||
|
is ErrorArgs.General -> createGeneralErrorState(args)
|
||||||
|
is ErrorArgs.ShieldingGeneralError -> createGeneralShieldingErrorState(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSyncErrorState(args: ErrorArgs.SyncError) = ErrorState(
|
||||||
|
title = stringRes(R.string.error_sync_title),
|
||||||
|
message = stringRes(args.synchronizerError.getStackTrace(STACKTRACE_LIMIT).orEmpty()),
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes(R.string.general_ok),
|
||||||
|
onClick = { navigationRouter.back() }
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes(R.string.general_report),
|
||||||
|
onClick = { sendReportClick(args) }
|
||||||
|
),
|
||||||
|
onBack = ::onBack,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createShieldingErrorState(args: ErrorArgs.ShieldingError) = ErrorState(
|
||||||
|
title = stringRes(R.string.error_shielding_title),
|
||||||
|
message = when (args.error) {
|
||||||
|
is SubmitResult.MultipleTrxFailure -> stringRes(R.string.error_shielding_message_grpc)
|
||||||
|
is SubmitResult.SimpleTrxFailure -> stringRes(
|
||||||
|
R.string.error_shielding_message,
|
||||||
|
stringRes(args.error.toErrorStacktrace())
|
||||||
|
)
|
||||||
|
},
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes(R.string.general_ok),
|
||||||
|
onClick = { navigationRouter.back() }
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes(R.string.general_report),
|
||||||
|
onClick = { sendReportClick(args) }
|
||||||
|
),
|
||||||
|
onBack = ::onBack,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createGeneralErrorState(args: ErrorArgs.General) = ErrorState(
|
||||||
|
title = stringRes(R.string.error_general_title),
|
||||||
|
message = stringRes(R.string.error_general_message),
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes(R.string.general_ok),
|
||||||
|
onClick = { navigationRouter.back() }
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes(R.string.general_report),
|
||||||
|
onClick = { sendReportClick(args.exception) }
|
||||||
|
),
|
||||||
|
onBack = ::onBack,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createGeneralShieldingErrorState(args: ErrorArgs.ShieldingGeneralError) = ErrorState(
|
||||||
|
title = stringRes(R.string.error_shielding_title),
|
||||||
|
message = stringRes(
|
||||||
|
R.string.error_shielding_message,
|
||||||
|
stringRes(args.exception.stackTraceToString().take(STACKTRACE_LIMIT))
|
||||||
|
),
|
||||||
|
positive = ButtonState(
|
||||||
|
text = stringRes(R.string.general_ok),
|
||||||
|
onClick = { navigationRouter.back() }
|
||||||
|
),
|
||||||
|
negative = ButtonState(
|
||||||
|
text = stringRes(R.string.general_report),
|
||||||
|
onClick = { sendReportClick(args.exception) }
|
||||||
|
),
|
||||||
|
onBack = ::onBack,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun sendReportClick(args: ErrorArgs.ShieldingError) = viewModelScope.launch {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
navigationRouter.back()
|
||||||
|
sendEmailUseCase(args.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendReportClick(args: ErrorArgs.SyncError) = viewModelScope.launch {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
navigationRouter.back()
|
||||||
|
sendEmailUseCase(args.synchronizerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendReportClick(exception: Exception) = viewModelScope.launch {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
navigationRouter.back()
|
||||||
|
sendEmailUseCase(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,13 @@ import co.electriccoin.zcash.ui.common.model.DistributionDimension
|
||||||
import co.electriccoin.zcash.ui.common.model.KeystoneAccount
|
import co.electriccoin.zcash.ui.common.model.KeystoneAccount
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletAccount
|
import co.electriccoin.zcash.ui.common.model.WalletAccount
|
||||||
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
||||||
|
import co.electriccoin.zcash.ui.common.repository.HomeMessageData
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.ErrorArgs
|
||||||
import co.electriccoin.zcash.ui.common.usecase.GetHomeMessageUseCase
|
import co.electriccoin.zcash.ui.common.usecase.GetHomeMessageUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
|
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
|
||||||
import co.electriccoin.zcash.ui.common.repository.HomeMessageData
|
|
||||||
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
|
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
|
||||||
|
import co.electriccoin.zcash.ui.common.usecase.NavigateToErrorUseCase
|
||||||
import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase
|
import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase
|
||||||
import co.electriccoin.zcash.ui.design.component.BigIconButtonState
|
import co.electriccoin.zcash.ui.design.component.BigIconButtonState
|
||||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
@ -56,7 +58,8 @@ class HomeViewModel(
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase,
|
private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase,
|
||||||
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
|
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
|
||||||
private val shieldFunds: ShieldFundsUseCase
|
private val shieldFunds: ShieldFundsUseCase,
|
||||||
|
private val navigateToError: NavigateToErrorUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val messageState = getHomeMessage
|
private val messageState = getHomeMessage
|
||||||
|
@ -228,24 +231,6 @@ class HomeViewModel(
|
||||||
|
|
||||||
private fun onShieldFundsMessageButtonClick() = shieldFunds(navigateBackAfterSuccess = false)
|
private fun onShieldFundsMessageButtonClick() = shieldFunds(navigateBackAfterSuccess = false)
|
||||||
|
|
||||||
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) {
|
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) =
|
||||||
// statusText =
|
navigateToError(ErrorArgs.SyncError(homeMessageData.synchronizerError))
|
||||||
// context.getString(
|
|
||||||
// R.string.balances_status_error_simple,
|
|
||||||
// context.getString(R.string.app_name)
|
|
||||||
// )
|
|
||||||
// statusAction =
|
|
||||||
// StatusAction.Error(
|
|
||||||
// details =
|
|
||||||
// context.getString(
|
|
||||||
// R.string.balances_status_error_dialog_cause,
|
|
||||||
// walletSnapshot.synchronizerError.getCauseMessage()
|
|
||||||
// ?: context.getString(R.string.balances_status_error_dialog_cause_unknown),
|
|
||||||
// walletSnapshot.synchronizerError.getStackTrace(limit = STACKTRACE_LIMIT)
|
|
||||||
// ?: context.getString(R.string.balances_status_error_dialog_stacktrace_unknown)
|
|
||||||
// ),
|
|
||||||
// fullStackTrace = walletSnapshot.synchronizerError.getStackTrace(limit = null)
|
|
||||||
// )
|
|
||||||
// TODO()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
<string name="general_etc">…</string>
|
<string name="general_etc">…</string>
|
||||||
|
|
||||||
<string name="general_ok">OK</string>
|
<string name="general_ok">OK</string>
|
||||||
|
<string name="general_report">Report</string>
|
||||||
<string name="general_remind_me_later">Remind me later</string>
|
<string name="general_remind_me_later">Remind me later</string>
|
||||||
<string name="general_remind_me_in">Remind me in %s</string>
|
<string name="general_remind_me_in">Remind me in %s</string>
|
||||||
<string name="general_remind_me_in_two_days">two days</string>
|
<string name="general_remind_me_in_two_days">two days</string>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="error_general_title">An error occurred</string>
|
||||||
|
<string name="error_general_message">Unknown cause. Please contact our support team if the problem persists
|
||||||
|
.</string>
|
||||||
|
<string name="error_sync_title">Error during sync</string>
|
||||||
|
<string name="error_shielding_title">Shielding error</string>
|
||||||
|
<string name="error_shielding_message">An error happened during the last shielding transaction. You can try again
|
||||||
|
later or report this issue if the problem persists.\n\n%s</string>
|
||||||
|
<string name="error_shielding_message_grpc">An error happened during the last shielding transaction. We will try to resubmit the transaction later. If it fails, you may need to repeat the shielding attempt at a later time.</string>
|
||||||
|
</resources>
|
Loading…
Reference in New Issue