2024-03-04 07:53:30 -08:00
|
|
|
package co.electriccoin.zcash.ui.screen.chooseserver.view
|
|
|
|
|
|
|
|
import androidx.compose.animation.animateContentSize
|
|
|
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
import androidx.compose.foundation.text.KeyboardActions
|
|
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
|
|
import androidx.compose.foundation.verticalScroll
|
|
|
|
import androidx.compose.material3.Scaffold
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
import androidx.compose.runtime.mutableIntStateOf
|
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
import androidx.compose.runtime.saveable.rememberSaveable
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
|
|
import androidx.compose.ui.platform.testTag
|
|
|
|
import androidx.compose.ui.res.stringResource
|
2024-04-01 23:29:08 -07:00
|
|
|
import androidx.compose.ui.text.font.FontStyle
|
2024-03-04 07:53:30 -08:00
|
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
|
|
import androidx.compose.ui.text.input.KeyboardType
|
|
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
|
|
import cash.z.ecc.android.sdk.internal.Twig
|
|
|
|
import cash.z.ecc.android.sdk.model.PersistableWallet
|
|
|
|
import cash.z.ecc.android.sdk.type.ServerValidation
|
|
|
|
import cash.z.ecc.sdk.extension.isValid
|
|
|
|
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
|
|
|
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
|
|
|
|
import co.electriccoin.zcash.ui.R
|
2024-04-05 04:09:08 -07:00
|
|
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
2024-03-04 08:45:51 -08:00
|
|
|
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
|
2024-03-04 07:53:30 -08:00
|
|
|
import co.electriccoin.zcash.ui.design.component.FormTextField
|
|
|
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
|
|
|
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
|
|
|
import co.electriccoin.zcash.ui.design.component.RadioButton
|
|
|
|
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
|
|
|
import co.electriccoin.zcash.ui.design.component.SubHeader
|
|
|
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
|
|
|
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerTag
|
2024-03-04 08:45:51 -08:00
|
|
|
import co.electriccoin.zcash.ui.screen.chooseserver.validateCustomServerValue
|
2024-03-04 07:53:30 -08:00
|
|
|
import kotlinx.collections.immutable.ImmutableList
|
|
|
|
import kotlinx.collections.immutable.toImmutableList
|
|
|
|
|
|
|
|
@Preview("Choose Server")
|
|
|
|
@Composable
|
|
|
|
private fun PreviewChooseServer() {
|
|
|
|
ZcashTheme(forceDarkMode = false) {
|
|
|
|
GradientSurface {
|
|
|
|
ChooseServer(
|
|
|
|
availableServers = emptyList<LightWalletEndpoint>().toImmutableList(),
|
|
|
|
onBack = {},
|
|
|
|
onServerChange = {},
|
|
|
|
validationResult = ServerValidation.Valid,
|
|
|
|
wallet = PersistableWalletFixture.new(),
|
2024-03-04 08:45:51 -08:00
|
|
|
isShowingErrorDialog = false,
|
|
|
|
setShowErrorDialog = {},
|
|
|
|
isShowingSuccessDialog = false,
|
|
|
|
setShowSuccessDialog = {},
|
2024-04-05 04:09:08 -07:00
|
|
|
walletRestoringState = WalletRestoringState.NONE,
|
2024-03-04 07:53:30 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
fun ChooseServer(
|
|
|
|
availableServers: ImmutableList<LightWalletEndpoint>,
|
|
|
|
onBack: () -> Unit,
|
|
|
|
onServerChange: (LightWalletEndpoint) -> Unit,
|
|
|
|
validationResult: ServerValidation,
|
|
|
|
wallet: PersistableWallet,
|
2024-03-04 08:45:51 -08:00
|
|
|
isShowingErrorDialog: Boolean,
|
|
|
|
setShowErrorDialog: (Boolean) -> Unit,
|
|
|
|
isShowingSuccessDialog: Boolean,
|
|
|
|
setShowSuccessDialog: (Boolean) -> Unit,
|
2024-04-05 04:09:08 -07:00
|
|
|
walletRestoringState: WalletRestoringState,
|
2024-03-04 07:53:30 -08:00
|
|
|
) {
|
|
|
|
Scaffold(
|
|
|
|
topBar = {
|
2024-04-05 04:09:08 -07:00
|
|
|
ChooseServerTopAppBar(
|
|
|
|
onBack = onBack,
|
|
|
|
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
|
|
|
)
|
2024-03-04 08:45:51 -08:00
|
|
|
}
|
2024-03-04 07:53:30 -08:00
|
|
|
) { paddingValues ->
|
|
|
|
ChooseServerMainContent(
|
|
|
|
modifier =
|
|
|
|
Modifier
|
|
|
|
.verticalScroll(
|
|
|
|
rememberScrollState()
|
|
|
|
)
|
|
|
|
.padding(
|
|
|
|
top = paddingValues.calculateTopPadding(),
|
|
|
|
bottom = paddingValues.calculateBottomPadding(),
|
|
|
|
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
|
|
|
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
|
|
|
)
|
|
|
|
.fillMaxWidth(),
|
|
|
|
availableServers = availableServers,
|
|
|
|
onServerChange = onServerChange,
|
2024-03-04 08:45:51 -08:00
|
|
|
setShowErrorDialog = setShowErrorDialog,
|
2024-03-04 07:53:30 -08:00
|
|
|
validationResult = validationResult,
|
|
|
|
wallet = wallet,
|
|
|
|
)
|
2024-03-04 08:45:51 -08:00
|
|
|
|
|
|
|
// Show validation popups
|
2024-04-01 23:29:08 -07:00
|
|
|
if (isShowingErrorDialog && validationResult is ServerValidation.InValid) {
|
2024-03-04 08:45:51 -08:00
|
|
|
ValidationErrorDialog(
|
2024-04-01 23:29:08 -07:00
|
|
|
reason = validationResult.reason.message,
|
2024-03-04 08:45:51 -08:00
|
|
|
onDone = { setShowErrorDialog(false) }
|
|
|
|
)
|
|
|
|
} else if (isShowingSuccessDialog) {
|
|
|
|
SaveSuccessDialog(
|
|
|
|
onDone = { setShowSuccessDialog(false) }
|
|
|
|
)
|
|
|
|
}
|
2024-03-04 07:53:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2024-04-05 04:09:08 -07:00
|
|
|
private fun ChooseServerTopAppBar(
|
|
|
|
onBack: () -> Unit,
|
|
|
|
showRestoring: Boolean
|
|
|
|
) {
|
2024-03-04 07:53:30 -08:00
|
|
|
SmallTopAppBar(
|
2024-04-05 04:09:08 -07:00
|
|
|
restoringLabel =
|
|
|
|
if (showRestoring) {
|
|
|
|
stringResource(id = R.string.restoring_wallet_label)
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
},
|
|
|
|
modifier = Modifier.testTag(ChooseServerTag.CHOOSE_SERVER_TOP_APP_BAR),
|
|
|
|
showTitleLogo = true,
|
2024-03-04 07:53:30 -08:00
|
|
|
backText = stringResource(id = R.string.choose_server_back).uppercase(),
|
|
|
|
backContentDescriptionText = stringResource(R.string.choose_server_back_content_description),
|
|
|
|
onBack = onBack,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2024-03-04 08:45:51 -08:00
|
|
|
@Suppress("LongMethod", "LongParameterList")
|
2024-03-04 07:53:30 -08:00
|
|
|
private fun ChooseServerMainContent(
|
|
|
|
availableServers: ImmutableList<LightWalletEndpoint>,
|
|
|
|
onServerChange: (LightWalletEndpoint) -> Unit,
|
|
|
|
validationResult: ServerValidation,
|
|
|
|
wallet: PersistableWallet,
|
|
|
|
modifier: Modifier = Modifier,
|
2024-03-04 08:45:51 -08:00
|
|
|
setShowErrorDialog: (Boolean) -> Unit,
|
2024-03-04 07:53:30 -08:00
|
|
|
) {
|
|
|
|
val options =
|
|
|
|
availableServers.toMutableList().apply {
|
2024-03-04 08:45:51 -08:00
|
|
|
// Note that this comparison could lead to a match with any predefined server endpoint even though the user
|
|
|
|
// previously pasted it as a custom one, which is fine for now and will be addressed when a dynamic
|
|
|
|
// server list obtaining is implemented.
|
2024-03-04 07:53:30 -08:00
|
|
|
if (contains(wallet.endpoint)) {
|
|
|
|
// We define the custom server as secured by default
|
|
|
|
add(LightWalletEndpoint("", -1, true))
|
|
|
|
} else {
|
|
|
|
// Adding previously chosen custom endpoint
|
|
|
|
add(wallet.endpoint)
|
|
|
|
}
|
|
|
|
}.toImmutableList()
|
|
|
|
|
|
|
|
val (selectedOption, setSelectedOption) =
|
|
|
|
rememberSaveable {
|
|
|
|
mutableIntStateOf(options.indexOf(wallet.endpoint))
|
|
|
|
}
|
|
|
|
|
|
|
|
val initialCustomServerValue =
|
|
|
|
options.last().run {
|
|
|
|
if (options.last().isValid()) {
|
|
|
|
stringResource(R.string.choose_server_textfield_value, options.last().host, options.last().port)
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val (customServerValue, setCustomServerValue) =
|
|
|
|
rememberSaveable {
|
|
|
|
mutableStateOf(initialCustomServerValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
Column(modifier = modifier) {
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
|
|
|
|
|
|
|
SubHeader(
|
|
|
|
text = stringResource(id = R.string.choose_server_title),
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
|
|
|
|
|
|
|
ServerList(
|
|
|
|
options = options,
|
|
|
|
selectedOption = selectedOption,
|
|
|
|
setSelectedOption = setSelectedOption,
|
|
|
|
customServerValue = customServerValue,
|
|
|
|
setCustomServerValue = setCustomServerValue,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
|
|
|
|
|
|
|
SaveButton(
|
|
|
|
enabled = validationResult != ServerValidation.Running,
|
|
|
|
options = options,
|
|
|
|
customServerValue = customServerValue,
|
|
|
|
onServerChange = {
|
|
|
|
// Check if the selected is different from the current
|
|
|
|
if (it != wallet.endpoint) {
|
|
|
|
onServerChange(it)
|
|
|
|
}
|
|
|
|
},
|
2024-03-04 08:45:51 -08:00
|
|
|
setShowErrorDialog = setShowErrorDialog,
|
2024-03-04 07:53:30 -08:00
|
|
|
selectedOption = selectedOption,
|
2024-03-25 12:50:31 -07:00
|
|
|
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingUpLarge)
|
2024-03-04 07:53:30 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@OptIn(ExperimentalFoundationApi::class)
|
|
|
|
@Composable
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
fun ServerList(
|
|
|
|
options: ImmutableList<LightWalletEndpoint>,
|
|
|
|
customServerValue: String,
|
|
|
|
setCustomServerValue: (String) -> Unit,
|
|
|
|
selectedOption: Int,
|
|
|
|
setSelectedOption: (Int) -> Unit,
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
) {
|
|
|
|
Column(modifier = modifier) {
|
|
|
|
options.forEachIndexed { index, endpoint ->
|
|
|
|
val isSelected = index == selectedOption
|
|
|
|
|
|
|
|
if (index == options.lastIndex) {
|
|
|
|
Column(
|
|
|
|
modifier = Modifier.animateContentSize()
|
|
|
|
) {
|
|
|
|
LabeledRadioButton(
|
|
|
|
endpoint = endpoint,
|
|
|
|
changeClick = { setSelectedOption(index) },
|
|
|
|
name = stringResource(id = R.string.choose_server_custom),
|
|
|
|
selected = isSelected,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
|
|
|
|
if (isSelected) {
|
|
|
|
val focusManager = LocalFocusManager.current
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
|
|
|
|
|
|
FormTextField(
|
|
|
|
value = customServerValue,
|
|
|
|
onValueChange = {
|
|
|
|
setCustomServerValue(it)
|
|
|
|
},
|
|
|
|
placeholder = {
|
|
|
|
Text(text = stringResource(R.string.choose_server_textfield_hint))
|
|
|
|
},
|
|
|
|
keyboardActions =
|
|
|
|
KeyboardActions(
|
|
|
|
onDone = {
|
|
|
|
focusManager.clearFocus(true)
|
|
|
|
}
|
|
|
|
),
|
|
|
|
keyboardOptions =
|
|
|
|
KeyboardOptions(
|
|
|
|
keyboardType = KeyboardType.Uri,
|
|
|
|
imeAction = ImeAction.Done
|
|
|
|
),
|
|
|
|
modifier =
|
|
|
|
Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(horizontal = ZcashTheme.dimens.spacingSmall)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LabeledRadioButton(
|
|
|
|
endpoint = endpoint,
|
|
|
|
changeClick = { setSelectedOption(index) },
|
|
|
|
name = stringResource(id = R.string.choose_server_textfield_value, endpoint.host, endpoint.port),
|
|
|
|
selected = isSelected,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun String.toEndpoint(delimiter: String): LightWalletEndpoint {
|
|
|
|
val parts = split(delimiter)
|
|
|
|
return LightWalletEndpoint(parts[0], parts[1].toInt(), true)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun LabeledRadioButton(
|
|
|
|
name: String,
|
|
|
|
endpoint: LightWalletEndpoint,
|
|
|
|
selected: Boolean,
|
|
|
|
changeClick: (LightWalletEndpoint) -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
) {
|
|
|
|
RadioButton(
|
|
|
|
text = name,
|
|
|
|
selected = selected,
|
|
|
|
onClick = { changeClick(endpoint) },
|
|
|
|
modifier = modifier
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
fun SaveButton(
|
|
|
|
enabled: Boolean,
|
|
|
|
customServerValue: String,
|
|
|
|
onServerChange: (LightWalletEndpoint) -> Unit,
|
|
|
|
options: ImmutableList<LightWalletEndpoint>,
|
|
|
|
selectedOption: Int,
|
|
|
|
modifier: Modifier = Modifier,
|
2024-03-04 08:45:51 -08:00
|
|
|
setShowErrorDialog: (Boolean) -> Unit,
|
2024-03-04 07:53:30 -08:00
|
|
|
) {
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
|
|
|
PrimaryButton(
|
|
|
|
enabled = enabled,
|
|
|
|
showProgressBar = !enabled,
|
|
|
|
text = stringResource(id = R.string.choose_server_save),
|
|
|
|
onClick = {
|
|
|
|
val selectedServer =
|
|
|
|
if (selectedOption == options.lastIndex) {
|
|
|
|
if (!validateCustomServerValue(customServerValue)) {
|
2024-03-04 08:45:51 -08:00
|
|
|
setShowErrorDialog(true)
|
2024-03-04 07:53:30 -08:00
|
|
|
return@PrimaryButton
|
|
|
|
}
|
|
|
|
|
|
|
|
customServerValue.toEndpoint(context.getString(R.string.choose_server_custom_delimiter))
|
|
|
|
} else {
|
|
|
|
options[selectedOption]
|
|
|
|
}
|
|
|
|
|
|
|
|
Twig.info { "Choose Server: Selected server: $selectedServer" }
|
|
|
|
|
|
|
|
onServerChange(selectedServer)
|
|
|
|
},
|
2024-03-21 01:57:36 -07:00
|
|
|
modifier =
|
|
|
|
modifier.then(
|
|
|
|
Modifier.fillMaxWidth()
|
|
|
|
)
|
2024-03-04 07:53:30 -08:00
|
|
|
)
|
|
|
|
}
|
2024-03-04 08:45:51 -08:00
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun ValidationErrorDialog(
|
2024-04-01 23:29:08 -07:00
|
|
|
reason: String?,
|
2024-03-04 08:45:51 -08:00
|
|
|
onDone: () -> Unit
|
|
|
|
) {
|
2024-04-01 23:29:08 -07:00
|
|
|
// TODO [#1276]: Once we ensure that the reason contains a localized message, we can leverage it for the UI prompt
|
|
|
|
// TODO [#1276]: Consider adding support for a specific exception in AppAlertDialog
|
|
|
|
// TODO [#1276]: https://github.com/Electric-Coin-Company/zashi-android/issues/1276
|
2024-03-04 08:45:51 -08:00
|
|
|
|
|
|
|
AppAlertDialog(
|
|
|
|
title = stringResource(id = R.string.choose_server_validation_dialog_error_title),
|
2024-04-01 23:29:08 -07:00
|
|
|
text = {
|
|
|
|
Column(
|
|
|
|
Modifier.verticalScroll(rememberScrollState())
|
|
|
|
) {
|
|
|
|
Text(text = stringResource(id = R.string.choose_server_validation_dialog_error_text))
|
|
|
|
|
|
|
|
if (!reason.isNullOrEmpty()) {
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
|
|
|
|
|
|
|
Text(
|
|
|
|
text = reason,
|
|
|
|
fontStyle = FontStyle.Italic
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2024-03-04 08:45:51 -08:00
|
|
|
confirmButtonText = stringResource(id = R.string.choose_server_validation_dialog_error_btn),
|
|
|
|
onConfirmButtonClick = onDone
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun SaveSuccessDialog(onDone: () -> Unit) {
|
|
|
|
Twig.info { "Succeed with saving the selected endpoint" }
|
|
|
|
|
|
|
|
AppAlertDialog(
|
|
|
|
title = stringResource(id = R.string.choose_server_save_success_dialog_title),
|
|
|
|
text = stringResource(id = R.string.choose_server_save_success_dialog_text),
|
|
|
|
confirmButtonText = stringResource(id = R.string.choose_server_save_success_dialog_btn),
|
|
|
|
onConfirmButtonClick = onDone
|
|
|
|
)
|
|
|
|
}
|