[#1473] Dark theme QR codes
- Closes #1473 - These changes add dark theme QR codes to `QrCodeView` and `RequestQrCodeVew` for all use cases - Changelogs updated
This commit is contained in:
parent
805a1b26b7
commit
37a7a1e334
|
@ -10,6 +10,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
- Send Confirmation & Send Progress screens have been refactored
|
||||
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||
us better results in testing
|
||||
- Zashi now displays dark version of QR code in the dark theme on the QR Code and Request screens
|
||||
|
||||
### Fixed
|
||||
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||
|
|
|
@ -16,6 +16,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
||||
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||
us better results in testing
|
||||
- Zashi now displays dark version of QR code in the dark theme on the QR Code and Request screens
|
||||
|
||||
### Fixed
|
||||
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||
|
|
|
@ -16,6 +16,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
||||
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||
us better results in testing
|
||||
- Zashi now displays dark version of QR code in the dark theme on the QR Code and Request screens
|
||||
|
||||
### Fixed
|
||||
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||
|
|
|
@ -8,6 +8,7 @@ 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.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
|
@ -17,16 +18,18 @@ import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
|||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.util.AndroidQrCodeImageGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.JvmQrCodeGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.QrCodeColors
|
||||
import co.electriccoin.zcash.ui.design.util.orDark
|
||||
|
||||
@Composable
|
||||
fun ZashiQr(
|
||||
qrData: String,
|
||||
modifier: Modifier = Modifier,
|
||||
qrSize: Dp = ZashiQrDefaults.width
|
||||
qrSize: Dp = ZashiQrDefaults.width,
|
||||
colors: QrCodeColors = QrCodeDefaults.colors()
|
||||
) {
|
||||
val qrSizePx = with(LocalDensity.current) { qrSize.roundToPx() }
|
||||
val bitmap = getQrCode(qrData, qrSizePx)
|
||||
val bitmap = getQrCode(qrData, qrSizePx, colors)
|
||||
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
|
@ -47,10 +50,11 @@ fun ZashiQr(
|
|||
|
||||
private fun getQrCode(
|
||||
address: String,
|
||||
size: Int
|
||||
size: Int,
|
||||
colors: QrCodeColors
|
||||
): ImageBitmap {
|
||||
val qrCodePixelArray = JvmQrCodeGenerator.generate(address, size)
|
||||
return AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size)
|
||||
return AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size, colors)
|
||||
}
|
||||
|
||||
object ZashiQrDefaults {
|
||||
|
@ -60,3 +64,14 @@ object ZashiQrDefaults {
|
|||
}
|
||||
|
||||
private const val WIDTH_RATIO = 0.66
|
||||
|
||||
object QrCodeDefaults {
|
||||
@Composable
|
||||
fun colors(
|
||||
background: Color = Color.White orDark ZashiColors.Surfaces.bgPrimary,
|
||||
foreground: Color = Color.Black orDark Color.White
|
||||
) = QrCodeColors(
|
||||
background = background,
|
||||
foreground = foreground
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,34 @@
|
|||
package co.electriccoin.zcash.ui.design.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
|
||||
object AndroidQrCodeImageGenerator : QrCodeImageGenerator {
|
||||
override fun generate(
|
||||
bitArray: BooleanArray,
|
||||
sizePixels: Int
|
||||
sizePixels: Int,
|
||||
colors: QrCodeColors
|
||||
): ImageBitmap {
|
||||
val colorArray = bitArray.toBlackAndWhiteColorArray()
|
||||
val colorArray = bitArray.toThemeColorArray(colors)
|
||||
|
||||
return Bitmap.createBitmap(colorArray, sizePixels, sizePixels, Bitmap.Config.ARGB_8888)
|
||||
.asImageBitmap()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [#1473]: Dark mode QR codes for Receive screen
|
||||
// TODO [#1473]: https://github.com/Electric-Coin-Company/zashi-android/issues/1473
|
||||
private fun BooleanArray.toBlackAndWhiteColorArray() =
|
||||
private fun BooleanArray.toThemeColorArray(colors: QrCodeColors) =
|
||||
IntArray(size) {
|
||||
if (this[it]) {
|
||||
Color.BLACK
|
||||
colors.foreground.toArgb()
|
||||
} else {
|
||||
Color.WHITE
|
||||
colors.background.toArgb()
|
||||
}
|
||||
}
|
||||
|
||||
data class QrCodeColors(
|
||||
val background: Color,
|
||||
val foreground: Color
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.ui.graphics.ImageBitmap
|
|||
interface QrCodeImageGenerator {
|
||||
fun generate(
|
||||
bitArray: BooleanArray,
|
||||
sizePixels: Int
|
||||
sizePixels: Int,
|
||||
colors: QrCodeColors
|
||||
): ImageBitmap
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import co.electriccoin.zcash.ui.R
|
|||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
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.ZashiBadge
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBadgeColors
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBottomBar
|
||||
|
@ -63,6 +64,7 @@ import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
|||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.AndroidQrCodeImageGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.JvmQrCodeGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.QrCodeColors
|
||||
import co.electriccoin.zcash.ui.screen.qrcode.model.QrCodeState
|
||||
import co.electriccoin.zcash.ui.screen.qrcode.model.QrCodeType
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -127,11 +129,13 @@ internal fun QrCodeView(
|
|||
}
|
||||
is QrCodeState.Prepared -> {
|
||||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
val colors = QrCodeDefaults.colors()
|
||||
val qrCodeImage =
|
||||
remember {
|
||||
qrCodeForAddress(
|
||||
address = state.walletAddress.address,
|
||||
size = sizePixels
|
||||
size = sizePixels,
|
||||
colors = colors
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -441,11 +445,13 @@ private fun ColumnScope.QrCode(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
val colors = QrCodeDefaults.colors()
|
||||
val qrCodeImage =
|
||||
remember {
|
||||
qrCodeForAddress(
|
||||
address = walletAddress.address,
|
||||
size = sizePixels
|
||||
size = sizePixels,
|
||||
colors = colors
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -474,7 +480,7 @@ private fun ColumnScope.QrCode(
|
|||
)
|
||||
.background(
|
||||
if (isSystemInDarkTheme()) {
|
||||
ZashiColors.Surfaces.bgAlt
|
||||
ZashiColors.Surfaces.bgPrimary
|
||||
} else {
|
||||
ZashiColors.Surfaces.bgPrimary
|
||||
},
|
||||
|
@ -488,6 +494,7 @@ private fun ColumnScope.QrCode(
|
|||
private fun qrCodeForAddress(
|
||||
address: String,
|
||||
size: Int,
|
||||
colors: QrCodeColors,
|
||||
): ImageBitmap {
|
||||
// In the future, use actual/expect to switch QR code generator implementations for multiplatform
|
||||
|
||||
|
@ -497,7 +504,7 @@ private fun qrCodeForAddress(
|
|||
|
||||
val qrCodePixelArray = JvmQrCodeGenerator.generate(address, size)
|
||||
|
||||
return AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size)
|
||||
return AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size, colors)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -5,6 +5,7 @@ import cash.z.ecc.android.sdk.model.MonetarySeparators
|
|||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.sdk.type.ZcashCurrency
|
||||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.util.QrCodeColors
|
||||
|
||||
internal sealed class RequestState {
|
||||
data object Loading : RequestState()
|
||||
|
@ -37,7 +38,7 @@ internal sealed class RequestState {
|
|||
val request: Request,
|
||||
val walletAddress: WalletAddress,
|
||||
val onQrCodeShare: (ImageBitmap) -> Unit,
|
||||
val onQrCodeGenerate: (pixels: Int) -> Unit,
|
||||
val onQrCodeGenerate: (pixels: Int, colors: QrCodeColors) -> Unit,
|
||||
override val onBack: () -> Unit,
|
||||
val onClose: () -> Unit,
|
||||
val zcashCurrency: ZcashCurrency,
|
||||
|
|
|
@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp
|
|||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.design.component.QrCodeDefaults
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBadge
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBadgeColors
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
@ -112,7 +113,8 @@ private fun ColumnScope.QrCode(
|
|||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
|
||||
if (state.request.qrCodeState.bitmap == null) {
|
||||
state.onQrCodeGenerate(sizePixels)
|
||||
val colors = QrCodeDefaults.colors()
|
||||
state.onQrCodeGenerate(sizePixels, colors)
|
||||
}
|
||||
|
||||
QrCode(
|
||||
|
@ -131,7 +133,7 @@ private fun ColumnScope.QrCode(
|
|||
)
|
||||
.background(
|
||||
if (isSystemInDarkTheme()) {
|
||||
ZashiColors.Surfaces.bgAlt
|
||||
ZashiColors.Surfaces.bgPrimary
|
||||
} else {
|
||||
ZashiColors.Surfaces.bgPrimary
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@ import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
|||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.util.AndroidQrCodeImageGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.JvmQrCodeGenerator
|
||||
import co.electriccoin.zcash.ui.design.util.QrCodeColors
|
||||
import co.electriccoin.zcash.ui.screen.qrcode.ext.fromReceiveAddressType
|
||||
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
|
||||
import co.electriccoin.zcash.ui.screen.request.ext.convertToDouble
|
||||
|
@ -136,7 +137,13 @@ class RequestViewModel(
|
|||
},
|
||||
walletAddress = walletAddress,
|
||||
request = request,
|
||||
onQrCodeGenerate = { qrCodeForValue(request.qrCodeState.requestUri, it) },
|
||||
onQrCodeGenerate = { pixels, colors ->
|
||||
qrCodeForValue(
|
||||
value = request.qrCodeState.requestUri,
|
||||
size = pixels,
|
||||
colors = colors,
|
||||
)
|
||||
},
|
||||
onQrCodeShare = { onRequestQrCodeShare(it, shareImageBitmap) },
|
||||
onBack = ::onBack,
|
||||
onClose = ::onClose,
|
||||
|
@ -444,6 +451,7 @@ class RequestViewModel(
|
|||
private fun qrCodeForValue(
|
||||
value: String,
|
||||
size: Int,
|
||||
colors: QrCodeColors
|
||||
) = viewModelScope.launch {
|
||||
// In the future, use actual/expect to switch QR code generator implementations for multiplatform
|
||||
|
||||
|
@ -452,7 +460,7 @@ class RequestViewModel(
|
|||
// small and we only generate QR codes infrequently.
|
||||
|
||||
val qrCodePixelArray = JvmQrCodeGenerator.generate(value, size)
|
||||
val bitmap = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size)
|
||||
val bitmap = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size, colors)
|
||||
|
||||
val newQrCodeState = request.value.qrCodeState.copy(bitmap = bitmap)
|
||||
request.emit(request.value.copy(qrCodeState = newQrCodeState))
|
||||
|
|
|
@ -95,7 +95,7 @@ private fun Bitmap.rotate(rotationDegrees: Int): Bitmap {
|
|||
width,
|
||||
// height
|
||||
height,
|
||||
// m
|
||||
// matrix
|
||||
matrix,
|
||||
// filter (Filter for better quality)
|
||||
true
|
||||
|
|
Loading…
Reference in New Issue