Error handling

This commit is contained in:
Milan Cerovsky 2025-04-14 13:09:22 +02:00
parent 3eadde6ddd
commit 8f526602ce
20 changed files with 550 additions and 103 deletions

View File

@ -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()
}
}
}

View File

@ -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,
)

View File

@ -49,6 +49,7 @@ android {
"src/main/res/ui/connect_keystone",
"src/main/res/ui/delete_wallet",
"src/main/res/ui/export_data",
"src/main/res/ui/error",
"src/main/res/ui/home",
"src/main/res/ui/choose_server",
"src/main/res/ui/integrations",

View File

@ -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.NavigateToAddressBookUseCase
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.NavigateToWalletBackupUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
@ -190,4 +191,5 @@ val useCaseModule =
factoryOf(::RemindWalletBackupLaterUseCase)
factoryOf(::RemindShieldFundsLaterUseCase)
singleOf(::ShieldFundsUseCase)
singleOf(::NavigateToErrorUseCase)
}

View File

@ -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.contact.viewmodel.AddContactViewModel
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.settings.ExchangeRateSettingsViewModel
import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel
@ -150,4 +151,5 @@ val viewModelModule =
viewModelOf(::ExchangeRateOptInViewModel)
viewModelOf(::ExchangeRateSettingsViewModel)
viewModelOf(::WalletBackupDetailViewModel)
viewModelOf(::ErrorViewModel)
}

View File

@ -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.deletewallet.WrapDeleteWallet
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.ExchangeRateOptIn
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidExchangeRateSettings
@ -471,6 +475,24 @@ internal fun MainActivity.Navigation() {
) {
AndroidWalletUpdatingInfo()
}
dialog<ErrorDialog>(
dialogProperties =
DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
AndroidErrorDialog()
}
dialog<ErrorBottomSheet>(
dialogProperties =
DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
AndroidErrorBottomSheet()
}
}
}

View File

@ -9,9 +9,9 @@ sealed interface SubmitResult {
data class MultipleTrxFailure(
val results: List<TransactionSubmitResult>
) : SubmitResult
) : Failure
sealed interface SimpleTrxFailure : SubmitResult {
sealed interface SimpleTrxFailure : Failure {
fun toErrorMessage(): String
fun toErrorStacktrace(): String
@ -45,4 +45,6 @@ sealed interface SubmitResult {
override fun toErrorStacktrace(): String = error.stackTraceToString()
}
}
sealed interface Failure : SubmitResult
}

View File

@ -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
}

View File

@ -5,6 +5,7 @@ import android.content.Intent
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import co.electriccoin.zcash.ui.R
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.getString
import co.electriccoin.zcash.ui.screen.support.model.SupportInfoType
@ -28,11 +29,47 @@ class SendEmailUseCase(
).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
runCatching { 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 =
when (submitResult) {
is SubmitResult.SimpleTrxFailure -> {
@ -41,6 +78,7 @@ class SendEmailUseCase(
supportInfo = submitResult.toErrorStacktrace()
)
}
is SubmitResult.MultipleTrxFailure -> {
EmailUtil.formatMessage(
prefix = context.getString(R.string.send_confirmation_multiple_report_text),
@ -48,9 +86,6 @@ class SendEmailUseCase(
suffix = submitResult.results.toSupportString(context)
)
}
else -> {
""
}
}
val mailIntent =

View File

@ -20,7 +20,8 @@ class ShieldFundsUseCase(
private val keystoneProposalRepository: KeystoneProposalRepository,
private val zashiProposalRepository: ZashiProposalRepository,
private val navigationRouter: NavigationRouter,
private val accountDataSource: AccountDataSource
private val accountDataSource: AccountDataSource,
private val navigateToError: NavigateToErrorUseCase,
) {
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
@ -30,6 +31,7 @@ class ShieldFundsUseCase(
is KeystoneAccount -> {
createKeystoneShieldProposal()
}
is ZashiAccount -> {
if (navigateBackAfterSuccess) {
navigationRouter.back()
@ -49,31 +51,11 @@ class ShieldFundsUseCase(
.first()
.submitResult
when (result) {
is SubmitResult.Success -> {
// 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
}
if (result is SubmitResult.Failure) {
navigateToError(ErrorArgs.ShieldingError(result))
}
} catch (e: Exception) {
navigateToError(ErrorArgs.ShieldingGeneralError(e))
} finally {
zashiProposalRepository.clear()
}
@ -86,16 +68,7 @@ class ShieldFundsUseCase(
navigationRouter.forward(SignKeystoneTransaction)
} catch (e: Exception) {
keystoneProposalRepository.clear()
// TODO messages
// showShieldingError(
// ShieldState.Failed(
// error =
// context.getString(
// R.string.balances_shielding_dialog_error_text_below_threshold
// ),
// stackTrace = ""
// )
// )
navigateToError(ErrorArgs.ShieldingGeneralError(e))
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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 = {}
)
)
}

View File

@ -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 = {}
)
)
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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.WalletAccount
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.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.common.repository.HomeMessageData
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
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.design.component.BigIconButtonState
import co.electriccoin.zcash.ui.design.util.stringRes
@ -56,7 +58,8 @@ class HomeViewModel(
private val navigationRouter: NavigationRouter,
private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase,
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
private val shieldFunds: ShieldFundsUseCase
private val shieldFunds: ShieldFundsUseCase,
private val navigateToError: NavigateToErrorUseCase
) : ViewModel() {
private val messageState = getHomeMessage
@ -228,24 +231,6 @@ class HomeViewModel(
private fun onShieldFundsMessageButtonClick() = shieldFunds(navigateBackAfterSuccess = false)
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) {
// statusText =
// 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()
}
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) =
navigateToError(ErrorArgs.SyncError(homeMessageData.synchronizerError))
}

View File

@ -29,6 +29,7 @@
<string name="general_etc"></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_in">Remind me in %s</string>
<string name="general_remind_me_in_two_days">two days</string>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -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>