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/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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 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 =
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.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))
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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