#1420 Design hotfixes

Closes #1420
This commit is contained in:
Milan Cerovsky 2024-09-05 13:19:31 +02:00
parent 95ac835b9c
commit 9c99e5ac80
7 changed files with 83 additions and 12 deletions

View File

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -46,7 +47,7 @@ fun RadioButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
checkedContent: @Composable () -> Unit = { RadioButtonCheckedContent(state) }, checkedContent: @Composable () -> Unit = { RadioButtonCheckedContent(state) },
uncheckedContent: @Composable () -> Unit = { RadioButtonUncheckedContent(state) }, uncheckedContent: @Composable () -> Unit = { RadioButtonUncheckedContent(state) },
trailingContent: @Composable (() -> Unit)? = null, trailingContent: @Composable (RowScope.() -> Unit)? = null,
testTag: String? = null, testTag: String? = null,
) { ) {
Row( Row(

View File

@ -1,6 +1,7 @@
package co.electriccoin.zcash.di package co.electriccoin.zcash.di
import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
@ -21,4 +22,5 @@ val useCaseModule =
singleOf(::PersistEndpointUseCase) singleOf(::PersistEndpointUseCase)
singleOf(::ValidateEndpointUseCase) singleOf(::ValidateEndpointUseCase)
singleOf(::GetPersistableWalletUseCase) singleOf(::GetPersistableWalletUseCase)
singleOf(::GetSelectedEndpointUseCase)
} }

View File

@ -131,7 +131,7 @@ class WalletRepositoryImpl(
.withIndex() .withIndex()
.flatMapLatest { (_, synchronizer) -> .flatMapLatest { (_, synchronizer) ->
synchronizer synchronizer
?.getFastestServers(application, getAllServers()) ?.getFastestServers(application, getDefaultServers())
?.map { ?.map {
when (it) { when (it) {
FastestServersResult.Measuring -> FastestServersResult.Measuring ->

View File

@ -0,0 +1,9 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.repository.WalletRepository
class GetSelectedEndpointUseCase(
private val walletRepository: WalletRepository
) {
suspend operator fun invoke() = walletRepository.getSelectedServer()
}

View File

@ -43,6 +43,8 @@ sealed interface ServerState : Itemizable {
data class Custom( data class Custom(
val radioButtonState: RadioButtonState, val radioButtonState: RadioButtonState,
val newServerTextFieldState: TextFieldState, val newServerTextFieldState: TextFieldState,
val badge: StringResource?,
val isExpanded: Boolean,
) : ServerState { ) : ServerState {
override val contentType: Any = "Custom" override val contentType: Any = "Custom"
override val key: Any = contentType override val key: Any = contentType

View File

@ -35,6 +35,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -324,7 +325,8 @@ private fun FastestServersHeader(state: ServerListState.Fastest) {
} else { } else {
Image( Image(
painter = painterResource(id = R.drawable.ic_retry), painter = painterResource(id = R.drawable.ic_retry),
contentDescription = state.retryButton.text.getValue() contentDescription = state.retryButton.text.getValue(),
colorFilter = ColorFilter.tint(ZcashTheme.zashiColors.textPrimary)
) )
} }
} }
@ -353,6 +355,7 @@ private fun ServerHeader(text: StringResource) {
) )
} }
@Suppress("LongMethod")
@Composable @Composable
private fun CustomServerRadioButton( private fun CustomServerRadioButton(
state: ServerState.Custom, state: ServerState.Custom,
@ -362,21 +365,50 @@ private fun CustomServerRadioButton(
RadioButton( RadioButton(
state = state.radioButtonState, state = state.radioButtonState,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
checkedContent = {
if (state.badge == null) {
RadioButtonCheckedContent(state.radioButtonState)
} else {
Image(
painter =
painterResource(
id =
if (isSystemInDarkTheme()) {
drawable.ic_radio_button_checked_variant_dark
} else {
drawable.ic_radio_button_checked_variant
}
),
contentDescription = state.radioButtonState.text.getValue(),
)
}
},
trailingContent = { trailingContent = {
if (state.badge != null) {
Badge(
text = state.badge,
)
Spacer(
modifier = Modifier.width(8.dp),
)
}
val iconAngle = val iconAngle =
animateFloatAsState( animateFloatAsState(
targetValue = if (state.radioButtonState.isChecked) 180f else 0f, targetValue = if (state.isExpanded) 180f else 0f,
label = "iconAngle" label = "iconAngle"
) )
Image( Image(
modifier = Modifier.rotate(iconAngle.value), modifier =
Modifier
.align(Alignment.CenterVertically)
.rotate(iconAngle.value),
painter = painterResource(id = R.drawable.ic_expand), painter = painterResource(id = R.drawable.ic_expand),
contentDescription = state.radioButtonState.text.getValue() contentDescription = state.radioButtonState.text.getValue()
) )
} }
) )
AnimatedVisibility(visible = state.radioButtonState.isChecked) { AnimatedVisibility(visible = state.isExpanded) {
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall)) Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
ZashiTextField( ZashiTextField(
@ -459,6 +491,8 @@ private fun ChooseServerPreview(
isEnabled = true, isEnabled = true,
onValueChange = { }, onValueChange = { },
), ),
badge = null,
isExpanded = true
) )
} else { } else {
ServerState.Default( ServerState.Default(

View File

@ -42,6 +42,8 @@ class ChooseServerViewModel(
private val dialogState = MutableStateFlow<ServerDialogState?>(null) private val dialogState = MutableStateFlow<ServerDialogState?>(null)
private val isCustomEndpointExpanded = MutableStateFlow(false)
private val fastest = private val fastest =
combine( combine(
observeSelectedEndpoint(), observeSelectedEndpoint(),
@ -71,7 +73,8 @@ class ChooseServerViewModel(
observeFastestServers(), observeFastestServers(),
userCustomEndpointText, userCustomEndpointText,
userEndpointSelection, userEndpointSelection,
) { selectedEndpoint, fastest, userCustomEndpointText, userEndpointSelection -> isCustomEndpointExpanded
) { selectedEndpoint, fastest, userCustomEndpointText, userEndpointSelection, isCustomEndpointExpanded ->
if (selectedEndpoint == null) return@combine null if (selectedEndpoint == null) return@combine null
val isSelectedEndpointCustom = !getAvailableServers().contains(selectedEndpoint) val isSelectedEndpointCustom = !getAvailableServers().contains(selectedEndpoint)
@ -81,7 +84,8 @@ class ChooseServerViewModel(
userEndpointSelection = userEndpointSelection, userEndpointSelection = userEndpointSelection,
isSelectedEndpointCustom = isSelectedEndpointCustom, isSelectedEndpointCustom = isSelectedEndpointCustom,
userCustomEndpointText = userCustomEndpointText, userCustomEndpointText = userCustomEndpointText,
selectedEndpoint = selectedEndpoint selectedEndpoint = selectedEndpoint,
isCustomEndpointExpanded = isCustomEndpointExpanded
) )
ServerListState.Other( ServerListState.Other(
@ -109,7 +113,15 @@ class ChooseServerViewModel(
userEndpointSelection, userEndpointSelection,
isSaveInProgress isSaveInProgress
) { selectedEndpoint, userEndpointSelection, isSaveInProgress -> ) { selectedEndpoint, userEndpointSelection, isSaveInProgress ->
val userSelectedEndpoint = (userEndpointSelection as? Selection.Endpoint)?.endpoint val userSelectedEndpoint =
when (userEndpointSelection) {
Selection.Custom -> {
val isSelectedEndpointCustom = !getAvailableServers().contains(selectedEndpoint)
if (isSelectedEndpointCustom) selectedEndpoint else null
}
is Selection.Endpoint -> userEndpointSelection.endpoint
null -> null
}
ButtonState( ButtonState(
text = stringRes(R.string.choose_server_save), text = stringRes(R.string.choose_server_save),
isEnabled = userEndpointSelection != null && selectedEndpoint != userSelectedEndpoint, isEnabled = userEndpointSelection != null && selectedEndpoint != userSelectedEndpoint,
@ -139,11 +151,17 @@ class ChooseServerViewModel(
userEndpointSelection: Selection?, userEndpointSelection: Selection?,
isSelectedEndpointCustom: Boolean, isSelectedEndpointCustom: Boolean,
userCustomEndpointText: String?, userCustomEndpointText: String?,
selectedEndpoint: LightWalletEndpoint selectedEndpoint: LightWalletEndpoint,
isCustomEndpointExpanded: Boolean,
) = ServerState.Custom( ) = ServerState.Custom(
radioButtonState = radioButtonState =
RadioButtonState( RadioButtonState(
text = stringRes(R.string.choose_server_custom), text =
if (isSelectedEndpointCustom) {
stringRes(R.string.choose_server_full_server_name, selectedEndpoint.host, selectedEndpoint.port)
} else {
stringRes(R.string.choose_server_custom)
},
isChecked = isChecked =
userEndpointSelection is Selection.Custom || userEndpointSelection is Selection.Custom ||
(userEndpointSelection == null && isSelectedEndpointCustom), (userEndpointSelection == null && isSelectedEndpointCustom),
@ -163,6 +181,8 @@ class ChooseServerViewModel(
}, },
onValueChange = ::onCustomEndpointTextChanged, onValueChange = ::onCustomEndpointTextChanged,
), ),
badge = if (isSelectedEndpointCustom) stringRes(R.string.choose_server_active) else null,
isExpanded = isCustomEndpointExpanded
) )
private fun createDefaultServerState( private fun createDefaultServerState(
@ -195,10 +215,12 @@ class ChooseServerViewModel(
} }
private fun onEndpointClicked(endpoint: LightWalletEndpoint) { private fun onEndpointClicked(endpoint: LightWalletEndpoint) {
isCustomEndpointExpanded.update { false }
userEndpointSelection.update { Selection.Endpoint(endpoint) } userEndpointSelection.update { Selection.Endpoint(endpoint) }
} }
private fun onCustomEndpointClicked() { private fun onCustomEndpointClicked() {
isCustomEndpointExpanded.update { true }
userEndpointSelection.update { Selection.Custom } userEndpointSelection.update { Selection.Custom }
} }
@ -210,6 +232,7 @@ class ChooseServerViewModel(
val selection = getUserEndpointSelectionOrShowError() ?: return@launch val selection = getUserEndpointSelectionOrShowError() ?: return@launch
try { try {
persistEndpoint(selection) persistEndpoint(selection)
isCustomEndpointExpanded.update { false }
} catch (e: PersistEndpointException) { } catch (e: PersistEndpointException) {
showValidationErrorDialog(e.message) showValidationErrorDialog(e.message)
} }
@ -228,7 +251,7 @@ class ChooseServerViewModel(
*/ */
private fun getUserEndpointSelectionOrShowError(): LightWalletEndpoint? { private fun getUserEndpointSelectionOrShowError(): LightWalletEndpoint? {
return when (val selection = userEndpointSelection.value) { return when (val selection = userEndpointSelection.value) {
Selection.Custom -> { is Selection.Custom -> {
val endpoint = userCustomEndpointText.value val endpoint = userCustomEndpointText.value
val validated = validateEndpoint(endpoint.orEmpty()) val validated = validateEndpoint(endpoint.orEmpty())
if (validated == null) { if (validated == null) {