Skip to content

Commit

Permalink
[#1737] Create transaction error handling
Browse files Browse the repository at this point in the history
* #1737 Create transaction error handling

Closes #1737

* QR code bitmap fix

Now we create the shareable bitmap on demand

---------

Co-authored-by: Milan Cerovsky <milan@z.cash>
  • Loading branch information
HonzaR and Milan-Cerovsky authored Jan 23, 2025
1 parent 2eb1958 commit 6c4986e
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package co.electriccoin.zcash.ui.screen.request.model

import android.content.Context
import androidx.compose.ui.graphics.ImageBitmap
import cash.z.ecc.android.sdk.ext.convertUsdToZec
import cash.z.ecc.android.sdk.ext.toZecString
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
Expand All @@ -11,7 +10,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.fromZecString
import cash.z.ecc.android.sdk.model.toFiatString
import co.electriccoin.zcash.ui.screen.request.ext.convertToDouble
import co.electriccoin.zcash.ui.screen.request.model.MemoState.Valid

data class Request(
val amountState: AmountState,
Expand Down Expand Up @@ -114,8 +112,5 @@ sealed class MemoState(
data class QrCodeState(
val requestUri: String,
val zecAmount: String,
val memo: String,
val bitmap: ImageBitmap?
) {
fun isValid(): Boolean = bitmap != null
}
val memo: String
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.screen.request.model

import androidx.compose.ui.graphics.ImageBitmap
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.sdk.type.ZcashCurrency
Expand Down Expand Up @@ -40,8 +39,7 @@ internal sealed class RequestState {
val request: Request,
val walletAddress: WalletAddress,
val onQrCodeClick: () -> Unit,
val onQrCodeShare: (ImageBitmap) -> Unit,
val onQrCodeGenerate: (pixels: Int, colors: QrCodeColors) -> Unit,
val onQrCodeShare: (colors: QrCodeColors, pixels: Int, uri: String) -> Unit,
override val onBack: () -> Unit,
val onClose: () -> Unit,
val zcashCurrency: ZcashCurrency,
Expand All @@ -50,7 +48,7 @@ internal sealed class RequestState {
contentDescription: StringResource? = null,
centerImageResId: Int? = null,
) = QrState(
qrData = walletAddress.address,
qrData = request.qrCodeState.requestUri,
onClick = onQrCodeClick,
contentDescription = contentDescription,
centerImageResId = centerImageResId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
Expand All @@ -30,6 +31,7 @@ import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.design.component.QrCodeDefaults
import co.electriccoin.zcash.ui.design.component.ZashiBottomBar
import co.electriccoin.zcash.ui.design.component.ZashiButton
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
Expand All @@ -43,6 +45,7 @@ import co.electriccoin.zcash.ui.screen.request.model.QrCodeState
import co.electriccoin.zcash.ui.screen.request.model.Request
import co.electriccoin.zcash.ui.screen.request.model.RequestCurrency
import co.electriccoin.zcash.ui.screen.request.model.RequestState
import kotlin.math.roundToInt

@Composable
@PreviewScreens
Expand Down Expand Up @@ -71,7 +74,6 @@ private fun RequestPreview() =
"zcash:t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBt",
"0.25",
memo = "Text memo",
null
),
),
exchangeRateState = ExchangeRateState.OptedOut,
Expand Down Expand Up @@ -172,11 +174,13 @@ private fun RequestBottomBar(
)
}
is RequestState.QrCode -> {
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
val colors = QrCodeDefaults.colors()

ZashiButton(
text = stringResource(id = R.string.request_qr_share_btn),
icon = R.drawable.ic_share,
enabled = state.request.qrCodeState.isValid(),
onClick = { state.onQrCodeShare(state.request.qrCodeState.bitmap!!) },
onClick = { state.onQrCodeShare(colors, sizePixels, state.request.qrCodeState.requestUri) },
modifier =
Modifier
.fillMaxWidth()
Expand All @@ -199,6 +203,8 @@ private fun RequestBottomBar(
}
}

private val DEFAULT_QR_CODE_SIZE = 320.dp

@Composable
private fun RequestContents(
state: RequestState.Prepared,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ import co.electriccoin.zcash.ui.screen.request.model.Request
import co.electriccoin.zcash.ui.screen.request.model.RequestCurrency
import co.electriccoin.zcash.ui.screen.request.model.RequestStage
import co.electriccoin.zcash.ui.screen.request.model.RequestState
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

Expand Down Expand Up @@ -67,7 +70,7 @@ class RequestViewModel(
Request(
amountState = AmountState.Default(defaultAmount, RequestCurrency.Zec),
memoState = MemoState.Valid(DEFAULT_MEMO, 0, defaultAmount),
qrCodeState = QrCodeState(DEFAULT_URI, defaultAmount, DEFAULT_MEMO, null),
qrCodeState = QrCodeState(DEFAULT_URI, defaultAmount, DEFAULT_MEMO),
)
)

Expand Down Expand Up @@ -137,18 +140,18 @@ class RequestViewModel(
},
walletAddress = walletAddress,
request = request,
onQrCodeGenerate = { pixels, colors ->
qrCodeForValue(
value = request.qrCodeState.requestUri,
size = pixels,
colors = colors,
)
},
onQrCodeClick = {
// TODO [#1731]: Allow QR codes colors switching
// TODO [#1731]: https://github.com/Electric-Coin-Company/zashi-android/issues/1731
},
onQrCodeShare = { onRequestQrCodeShare(it, shareImageBitmap) },
onQrCodeShare = { colors, pixels, uri ->
onShareQrCode(
colors = colors,
pixels = pixels,
requestUri = uri,
shareImageBitmap = shareImageBitmap
)
},
onBack = ::onBack,
onClose = ::onClose,
zcashCurrency = getZcashCurrency(),
Expand Down Expand Up @@ -270,6 +273,22 @@ class RequestViewModel(
} ?: newAmount
}

private fun onShareQrCode(
colors: QrCodeColors,
pixels: Int,
requestUri: String,
shareImageBitmap: ShareImageUseCase,
) = viewModelScope.launch {
bitmapForData(
value = requestUri,
size = pixels,
colors = colors,
).filterNotNull()
.collect { bitmap ->
onRequestQrCodeShare(bitmap, shareImageBitmap)
}
}

internal fun onBack() =
viewModelScope.launch {
when (stage.value) {
Expand Down Expand Up @@ -329,7 +348,6 @@ class RequestViewModel(
),
zecAmount = request.value.memoState.zecAmount,
memo = request.value.memoState.text,
bitmap = null
)
)
)
Expand Down Expand Up @@ -365,7 +383,6 @@ class RequestViewModel(
),
zecAmount = qrCodeAmount,
memo = DEFAULT_MEMO,
bitmap = null
)
)
request.emit(newRequest)
Expand Down Expand Up @@ -452,22 +469,26 @@ class RequestViewModel(
}
}

private fun qrCodeForValue(
private fun bitmapForData(
value: String,
size: Int,
colors: QrCodeColors
) = viewModelScope.launch {
// In the future, use actual/expect to switch QR code generator implementations for multiplatform
) = callbackFlow {
viewModelScope.launch {
// In the future, use actual/expect to switch QR code generator implementations for multiplatform

// Note that our implementation has an extra array copy to BooleanArray, which is a cross-platform
// representation. This should have minimal performance impact since the QR code is relatively
// small and we only generate QR codes infrequently.
// Note that our implementation has an extra array copy to BooleanArray, which is a cross-platform
// representation. This should have minimal performance impact since the QR code is relatively
// small and we only generate QR codes infrequently.

val qrCodePixelArray = JvmQrCodeGenerator.generate(value, size)
val bitmap = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size, colors)
val qrCodePixelArray = JvmQrCodeGenerator.generate(value, size)
val bitmap = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size, colors)

val newQrCodeState = request.value.qrCodeState.copy(bitmap = bitmap)
request.emit(request.value.copy(qrCodeState = newQrCodeState))
trySend(bitmap)
}
awaitClose {
// No resources to release
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class SendViewModel(
try {
createProposal(newZecSend)
} catch (e: Exception) {
setSendStage(SendStage.SendFailure(""))
setSendStage(SendStage.SendFailure(e.cause?.message ?: e.message ?: ""))
Twig.error(e) { "Error creating proposal" }
}
}
Expand All @@ -151,7 +151,7 @@ class SendViewModel(
try {
createKeystoneZip321TransactionProposal(zip321Uri)
} catch (e: Exception) {
setSendStage(SendStage.SendFailure(""))
setSendStage(SendStage.SendFailure(e.cause?.message ?: e.message ?: ""))
Twig.error(e) { "Error creating proposal" }
}
}
Expand Down

0 comments on commit 6c4986e

Please sign in to comment.