* #1763 Keystone confirmation flow design update Closes #1763 * #1763 Documentation update Closes #1763 * #1763 Documentation update Closes #1763 * #1763 Back handling * #1763 Strings update * #1763 Code cleanup
This commit is contained in:
parent
ed8dad3c54
commit
1ed5088953
|
@ -6,8 +6,12 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Confirm the rejection of a Keystone transaction dialog added.
|
||||
|
||||
### Changed
|
||||
- `Flexa` version has been bumped to 1.0.11
|
||||
- Keystone flows swapped the buttons for the better UX, the main CTA is the closes button for a thumb.
|
||||
|
||||
## [1.3.3 (839)] - 2025-01-23
|
||||
|
||||
|
|
|
@ -201,6 +201,22 @@ object ZashiButtonDefaults {
|
|||
borderColor = borderColor,
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun destructive2Colors(
|
||||
containerColor: Color = ZashiColors.Btns.Destructive2.btnDestroy2Bg,
|
||||
contentColor: Color = ZashiColors.Btns.Destructive2.btnDestroy2Fg,
|
||||
borderColor: Color = Color.Unspecified,
|
||||
disabledContainerColor: Color = ZashiColors.Btns.Destructive2.btnDestroy2BgDisabled,
|
||||
disabledContentColor: Color = ZashiColors.Btns.Destructive2.btnDestroy2FgDisabled,
|
||||
) = ZashiButtonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor,
|
||||
borderColor = borderColor,
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheetProperties
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T : ModalBottomSheetState> ZashiInScreenModalBottomSheet(
|
||||
state: T?,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||
content: @Composable (T) -> Unit = {},
|
||||
) {
|
||||
var normalizedState: T? by remember { mutableStateOf(null) }
|
||||
|
||||
normalizedState?.let {
|
||||
ZashiModalBottomSheet(
|
||||
onDismissRequest = {
|
||||
it.onBack()
|
||||
},
|
||||
modifier = modifier,
|
||||
sheetState = sheetState,
|
||||
properties =
|
||||
ModalBottomSheetProperties(
|
||||
shouldDismissOnBackPress = false
|
||||
)
|
||||
) {
|
||||
BackHandler {
|
||||
it.onBack()
|
||||
}
|
||||
|
||||
content(it)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state) {
|
||||
if (state != null) {
|
||||
normalizedState = state
|
||||
sheetState.show()
|
||||
} else {
|
||||
sheetState.hide()
|
||||
normalizedState = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ModalBottomSheetState {
|
||||
val onBack: () -> Unit
|
||||
}
|
|
@ -12,6 +12,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.ModalBottomSheetDefaults
|
||||
import androidx.compose.material3.ModalBottomSheetProperties
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
|
@ -36,6 +38,7 @@ fun ZashiModalBottomSheet(
|
|||
modifier: Modifier = Modifier,
|
||||
scrimColor: Color = BottomSheetDefaults.ScrimColor,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||
properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
|
@ -46,6 +49,7 @@ fun ZashiModalBottomSheet(
|
|||
shape = ZashiModalBottomSheetDefaults.SheetShape,
|
||||
containerColor = ZashiModalBottomSheetDefaults.ContainerColor,
|
||||
dragHandle = { ZashiModalBottomSheetDragHandle() },
|
||||
properties = properties,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -358,15 +358,15 @@ private fun AmountWidget(state: AmountState) {
|
|||
private fun BottomBar(state: ReviewTransactionState) {
|
||||
ZashiBottomBar {
|
||||
ZashiButton(
|
||||
state = state.primaryButton,
|
||||
state = state.negativeButton,
|
||||
colors = ZashiButtonDefaults.secondaryColors(),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
ZashiButton(
|
||||
state = state.negativeButton,
|
||||
colors = ZashiButtonDefaults.secondaryColors(),
|
||||
state = state.primaryButton,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
package co.electriccoin.zcash.ui.screen.signkeystonetransaction
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
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.screen.signkeystonetransaction.view.SignKeystoneTransactionBottomSheet
|
||||
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.view.SignKeystoneTransactionView
|
||||
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.viewmodel.SignKeystoneTransactionViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AndroidSignKeystoneTransaction() {
|
||||
val viewModel = koinViewModel<SignKeystoneTransactionViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val bottomSheetState by viewModel.bottomSheetState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler {
|
||||
state?.onBack?.invoke()
|
||||
|
@ -20,4 +24,8 @@ fun AndroidSignKeystoneTransaction() {
|
|||
state?.let {
|
||||
SignKeystoneTransactionView(it)
|
||||
}
|
||||
|
||||
SignKeystoneTransactionBottomSheet(
|
||||
state = bottomSheetState
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package co.electriccoin.zcash.ui.screen.signkeystonetransaction.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiInScreenModalBottomSheet
|
||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||
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.stringRes
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SignKeystoneTransactionBottomSheet(
|
||||
state: SignKeystoneTransactionBottomSheetState?,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||
) {
|
||||
ZashiInScreenModalBottomSheet(
|
||||
state = state,
|
||||
sheetState = sheetState,
|
||||
modifier = modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_keystone_sign_reject),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.sign_keystone_transaction_bottom_sheet_title),
|
||||
style = ZashiTypography.header6,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.sign_keystone_transaction_bottom_sheet_subtitle),
|
||||
style = ZashiTypography.textSm,
|
||||
color = ZashiColors.Text.textTertiary,
|
||||
)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = it.positiveButton
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = it.negativeButton,
|
||||
colors = ZashiButtonDefaults.destructive2Colors()
|
||||
)
|
||||
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class SignKeystoneTransactionBottomSheetState(
|
||||
override val onBack: () -> Unit,
|
||||
val positiveButton: ButtonState,
|
||||
val negativeButton: ButtonState,
|
||||
) : ModalBottomSheetState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
SignKeystoneTransactionBottomSheet(
|
||||
sheetState =
|
||||
rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
skipHiddenState = true,
|
||||
initialValue = SheetValue.Expanded,
|
||||
),
|
||||
state =
|
||||
SignKeystoneTransactionBottomSheetState(
|
||||
onBack = {},
|
||||
positiveButton = ButtonState(stringRes("Get Signature")),
|
||||
negativeButton = ButtonState(stringRes("Reject")),
|
||||
)
|
||||
)
|
||||
}
|
|
@ -163,12 +163,12 @@ private fun BottomSection(
|
|||
}
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.positiveButton
|
||||
state = state.negativeButton,
|
||||
colors = ZashiButtonDefaults.destructive1Colors()
|
||||
)
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.negativeButton,
|
||||
colors = ZashiButtonDefaults.secondaryColors()
|
||||
state = state.positiveButton
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,15 +18,19 @@ import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH
|
|||
import co.electriccoin.zcash.ui.screen.scankeystone.ScanKeystonePCZTRequest
|
||||
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.state.SignKeystoneTransactionState
|
||||
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.state.ZashiAccountInfoListItemState
|
||||
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.view.SignKeystoneTransactionBottomSheetState
|
||||
import com.sparrowwallet.hummingbird.UREncoder
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class SignKeystoneTransactionViewModel(
|
||||
observeSelectedWalletAccount: ObserveSelectedWalletAccountUseCase,
|
||||
|
@ -38,8 +42,37 @@ class SignKeystoneTransactionViewModel(
|
|||
) : ViewModel() {
|
||||
private var encoder: UREncoder? = null
|
||||
|
||||
private val isBottomSheetVisible = MutableStateFlow(false)
|
||||
|
||||
private val currentQrPart = MutableStateFlow<String?>(null)
|
||||
|
||||
val bottomSheetState =
|
||||
isBottomSheetVisible
|
||||
.map { isVisible ->
|
||||
if (isVisible) {
|
||||
SignKeystoneTransactionBottomSheetState(
|
||||
onBack = ::onCloseBottomSheetClick,
|
||||
positiveButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.sign_keystone_transaction_bottom_sheet_go_back),
|
||||
onClick = ::onCloseBottomSheetClick
|
||||
),
|
||||
negativeButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.sign_keystone_transaction_bottom_sheet_reject),
|
||||
onClick = ::onRejectBottomSheetClick
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
val state: StateFlow<SignKeystoneTransactionState?> =
|
||||
combine(
|
||||
observeProposalUseCase(),
|
||||
|
@ -81,7 +114,11 @@ class SignKeystoneTransactionViewModel(
|
|||
// TODO [#1731]: https://github.com/Electric-Coin-Company/zashi-android/issues/1731
|
||||
},
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
|
@ -94,17 +131,29 @@ class SignKeystoneTransactionViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onRejectBottomSheetClick() {
|
||||
viewModelScope.launch {
|
||||
isBottomSheetVisible.update { false }
|
||||
delay(350.milliseconds)
|
||||
cancelKeystoneProposalFlow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCloseBottomSheetClick() {
|
||||
isBottomSheetVisible.update { false }
|
||||
}
|
||||
|
||||
private fun onSharePCZTClick() =
|
||||
viewModelScope.launch {
|
||||
sharePCZT()
|
||||
}
|
||||
|
||||
private fun onBack() {
|
||||
cancelKeystoneProposalFlow()
|
||||
isBottomSheetVisible.update { !it }
|
||||
}
|
||||
|
||||
private fun onRejectClick() {
|
||||
cancelKeystoneProposalFlow()
|
||||
isBottomSheetVisible.update { true }
|
||||
}
|
||||
|
||||
private fun onSignTransactionClick() {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="45dp"
|
||||
android:height="44dp"
|
||||
android:viewportWidth="45"
|
||||
android:viewportHeight="44">
|
||||
<path
|
||||
android:pathData="M0.5,22C0.5,9.85 10.35,0 22.5,0C34.65,0 44.5,9.85 44.5,22C44.5,34.15 34.65,44 22.5,44C10.35,44 0.5,34.15 0.5,22Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="22.5"
|
||||
android:startY="0"
|
||||
android:endX="22.5"
|
||||
android:endY="44"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF55160C"/>
|
||||
<item android:offset="1" android:color="#FF7A271A"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M18.333,29.219C15.842,27.778 14.167,25.085 14.167,22C14.167,17.398 17.897,13.667 22.5,13.667C27.102,13.667 30.833,17.398 30.833,22C30.833,25.085 29.157,27.778 26.667,29.219M25.833,22L22.5,18.667M22.5,18.667L19.167,22M22.5,18.667V30.333"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#F04438"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,28 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="45dp"
|
||||
android:height="44dp"
|
||||
android:viewportWidth="45"
|
||||
android:viewportHeight="44">
|
||||
<path
|
||||
android:pathData="M0.5,22C0.5,9.85 10.35,0 22.5,0C34.65,0 44.5,9.85 44.5,22C44.5,34.15 34.65,44 22.5,44C10.35,44 0.5,34.15 0.5,22Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="22.5"
|
||||
android:startY="0"
|
||||
android:endX="22.5"
|
||||
android:endY="44"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFFEF3F2"/>
|
||||
<item android:offset="1" android:color="#FFFEE4E2"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M18.333,29.219C15.842,27.778 14.167,25.085 14.167,22C14.167,17.398 17.898,13.667 22.5,13.667C27.102,13.667 30.833,17.398 30.833,22C30.833,25.085 29.157,27.778 26.667,29.219M25.833,22L22.5,18.667M22.5,18.667L19.167,22M22.5,18.667V30.333"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#F04438"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -6,4 +6,8 @@
|
|||
<string name="sign_keystone_transaction_badge">Hardware</string>
|
||||
<string name="sign_keystone_transaction_positive">Obtener Firma</string>
|
||||
<string name="sign_keystone_transaction_negative">Rechazar</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_title">¿Estás seguro?</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_subtitle">Rechazar la firma cancelará la transacción y tendrás que empezar de nuevo si deseas continuar. Esta acción no se puede deshacer.</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_go_back">Volver</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_reject">Rechazar firma</string>
|
||||
</resources>
|
||||
|
|
|
@ -6,4 +6,8 @@
|
|||
<string name="sign_keystone_transaction_badge">Hardware</string>
|
||||
<string name="sign_keystone_transaction_positive">Get Signature</string>
|
||||
<string name="sign_keystone_transaction_negative">Reject</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_title">Are you sure?</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_subtitle">Rejecting the signature will cancel the transaction, and you’ll need to start over if you want to proceed. This action cannot be undone.</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_go_back">Go back</string>
|
||||
<string name="sign_keystone_transaction_bottom_sheet_reject">Reject Signature</string>
|
||||
</resources>
|
Loading…
Reference in New Issue