[#1737] Create transaction error handling
* #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>
This commit is contained in:
parent
2eb19586d5
commit
6c4986e729
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -71,7 +74,6 @@ private fun RequestPreview() =
|
|||
"zcash:t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBt",
|
||||
"0.25",
|
||||
memo = "Text memo",
|
||||
null
|
||||
),
|
||||
),
|
||||
exchangeRateState = ExchangeRateState.OptedOut,
|
||||
|
@ -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()
|
||||
|
@ -199,6 +203,8 @@ private fun RequestBottomBar(
|
|||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_QR_CODE_SIZE = 320.dp
|
||||
|
||||
@Composable
|
||||
private fun RequestContents(
|
||||
state: RequestState.Prepared,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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) {
|
||||
|
@ -329,7 +348,6 @@ class RequestViewModel(
|
|||
),
|
||||
zecAmount = request.value.memoState.zecAmount,
|
||||
memo = request.value.memoState.text,
|
||||
bitmap = null
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -365,7 +383,6 @@ class RequestViewModel(
|
|||
),
|
||||
zecAmount = qrCodeAmount,
|
||||
memo = DEFAULT_MEMO,
|
||||
bitmap = null
|
||||
)
|
||||
)
|
||||
request.emit(newRequest)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue