Skip to content

Commit

Permalink
Fix Contact selection in HSAddressInput
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelekol committed Jul 25, 2023
1 parent 523867b commit 2f8f45b
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package io.horizontalsystems.bankwallet.modules.address
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.Address
import io.horizontalsystems.marketkit.models.BlockchainType
import io.horizontalsystems.marketkit.models.TokenQuery

object AddressInputModule {

class FactoryToken(private val tokenQuery: TokenQuery, private val coinCode: String) : ViewModelProvider.Factory {
class FactoryToken(private val tokenQuery: TokenQuery, private val coinCode: String, private val initial: Address?) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val addressViewModel = AddressViewModel(tokenQuery.blockchainType, App.contactsRepository)
val addressViewModel = AddressViewModel(tokenQuery.blockchainType, App.contactsRepository, initial)

addressViewModel.addAddressHandler(AddressHandlerEns(EnsResolverHolder.resolver))
addressViewModel.addAddressHandler(AddressHandlerUdn(tokenQuery, coinCode))
Expand Down Expand Up @@ -52,7 +53,7 @@ object AddressInputModule {
class FactoryNft(private val blockchainType: BlockchainType) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val addressViewModel = AddressViewModel(blockchainType, App.contactsRepository)
val addressViewModel = AddressViewModel(blockchainType, App.contactsRepository, null)

addressViewModel.addAddressHandler(AddressHandlerEns(EnsResolverHolder.resolver))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
package io.horizontalsystems.bankwallet.modules.address

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.horizontalsystems.bankwallet.entities.Address
import io.horizontalsystems.bankwallet.entities.DataState
import io.horizontalsystems.bankwallet.modules.contacts.ContactsRepository
import io.horizontalsystems.marketkit.models.BlockchainType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class AddressViewModel(
val blockchainType: BlockchainType,
private val contactsRepository: ContactsRepository
private val contactsRepository: ContactsRepository,
initial: Address?
) : ViewModel() {

var address by mutableStateOf<Address?>(initial)
private set
var inputState by mutableStateOf<DataState<Address>?>(null)
private set
var value by mutableStateOf(initial?.hex ?: "")
private set
private val addressHandlers = mutableListOf<IAddressHandler>()

fun addAddressHandler(handler: IAddressHandler) {
Expand All @@ -21,24 +34,48 @@ class AddressViewModel(
fun hasContacts() =
contactsRepository.getContactsFiltered(blockchainType).isNotEmpty()

@Throws(AddressValidationException::class)
suspend fun parseAddress(value: String): Address = withContext(Dispatchers.IO) {
if (value.isBlank()) throw AddressValidationException.Blank()
fun parseAddressX(value: String) {
this.value = value

if (value.isBlank()) {
inputState = null
address = null
return
}

val vTrimmed = value.trim()

val handler = addressHandlers.firstOrNull {
inputState = DataState.Loading

viewModelScope.launch(Dispatchers.IO) {
val handler = addressHandlers.firstOrNull {
try {
it.isSupported(vTrimmed)
} catch (t: Throwable) {
false
}
} ?: run {
inputState = DataState.Error(AddressValidationException.Unsupported())
return@launch
}

try {
it.isSupported(vTrimmed)
val parsedAddress = handler.parseAddress(vTrimmed)
withContext(Dispatchers.Main) {
address = parsedAddress
inputState = DataState.Success(parsedAddress)
}
} catch (t: Throwable) {
false
withContext(Dispatchers.Main) {
inputState = DataState.Error(t)
}
}
} ?: throw AddressValidationException.Unsupported()
}
}

try {
handler.parseAddress(vTrimmed)
} catch (t: Throwable) {
throw AddressValidationException.Invalid(t)
fun onAddressError(addressError: Throwable?) {
addressError?.let {
inputState = DataState.Error(addressError)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
package io.horizontalsystems.bankwallet.modules.address

import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.entities.Address
import io.horizontalsystems.bankwallet.entities.DataState
import io.horizontalsystems.bankwallet.ui.compose.components.FormsInputAddress
import io.horizontalsystems.bankwallet.ui.compose.components.FormsInputStateWarning
import io.horizontalsystems.bankwallet.ui.compose.components.TextPreprocessor
import io.horizontalsystems.bankwallet.ui.compose.components.TextPreprocessorImpl
import io.horizontalsystems.marketkit.models.TokenQuery
import kotlinx.coroutines.Job
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch

@Composable
fun HSAddressInput(
Expand All @@ -35,123 +22,57 @@ fun HSAddressInput(
error: Throwable? = null,
textPreprocessor: TextPreprocessor = TextPreprocessorImpl,
navController: NavController,
onStateChange: ((DataState<Address>?) -> Unit)? = null,
onValueChange: ((Address?) -> Unit)? = null
onError: ((Throwable?) -> Unit)? = null,
onValueChange: ((Address?) -> Unit)? = null,
) {
val viewModel = viewModel<AddressViewModel>(
factory = AddressInputModule.FactoryToken(tokenQuery, coinCode),
key = "address_view_model_${tokenQuery.id}"
factory = AddressInputModule.FactoryToken(tokenQuery, coinCode, initial),
key = "address_view_model_${tokenQuery.id}"
)

HSAddressInput(
modifier = modifier,
viewModel = viewModel,
initial = initial,
error = error,
textPreprocessor = textPreprocessor,
navController = navController,
onStateChange = onStateChange,
onError = onError,
onValueChange = onValueChange
)
}

val DataStateAddressSaver: Saver<DataState<Address>?, Any> = run {
val addressKey = "data"
val loadingKey = "loading"
val errorKey = "error"
mapSaver(
save = {
val address = it?.dataOrNull
val error = (it?.errorOrNull as? Parcelable)
val loading = it?.loading

mapOf(
addressKey to address,
loadingKey to loading,
errorKey to error,
)
},
restore = {
val address = it[addressKey] as? Address
val loading = it[loadingKey] as? Boolean
val error = it[errorKey] as? Throwable

when {
address != null -> DataState.Success(address)
error != null -> DataState.Error(error)
loading == true -> DataState.Loading
else -> null
}
}
)
}
@Composable
fun HSAddressInput(
modifier: Modifier = Modifier,
viewModel: AddressViewModel,
initial: Address? = null,
error: Throwable? = null,
textPreprocessor: TextPreprocessor = TextPreprocessorImpl,
navController: NavController,
onStateChange: ((DataState<Address>?) -> Unit)? = null,
onValueChange: ((Address?) -> Unit)? = null
onError: ((Throwable?) -> Unit)? = null,
onValueChange: ((Address?) -> Unit)? = null,
) {
val scope = rememberCoroutineScope()
var addressState by rememberSaveable(stateSaver = DataStateAddressSaver) {
mutableStateOf(initial?.let { DataState.Success(it) })
LaunchedEffect(error) {
viewModel.onAddressError(error)
}
var parseAddressJob by remember { mutableStateOf<Job?>(null)}
var isFocused by remember { mutableStateOf(false)}

val addressStateMergedWithError = if (addressState is DataState.Success && error != null) {
DataState.Error(error)
} else {
addressState
LaunchedEffect(viewModel.address) {
onValueChange?.invoke(viewModel.address)
}

val inputState = when {
isFocused -> getFocusedState(addressStateMergedWithError)
else -> addressStateMergedWithError
LaunchedEffect(viewModel.inputState) {
onError?.invoke(viewModel.inputState?.errorOrNull)
}

FormsInputAddress(
modifier = modifier,
initial = initial?.title,
value = viewModel.value,
hint = stringResource(id = R.string.Watch_Address_Hint),
state = inputState,
state = viewModel.inputState,
textPreprocessor = textPreprocessor,
onChangeFocus = {
isFocused = it
},
navController = navController,
chooseContactEnable = viewModel.hasContacts(),
blockchainType = viewModel.blockchainType,
) {
parseAddressJob?.cancel()
parseAddressJob = scope.launch {
addressState = DataState.Loading

val state = try {
DataState.Success(viewModel.parseAddress(it))
} catch (e: AddressValidationException.Blank) {
null
} catch (e: AddressValidationException) {
DataState.Error(e)
}

ensureActive()
addressState = state
onValueChange?.invoke(addressState?.dataOrNull)
onStateChange?.invoke(addressState)
}
viewModel.parseAddressX(it)
}
}

private fun getFocusedState(state: DataState<Address>?): DataState<Address>? {
return if (state is DataState.Error &&
(state.error !is AddressValidationException.Invalid && state.error !is FormsInputStateWarning)) {
null
} else {
state
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ interface IRecipientAddressService {
val recipientAddressState: Observable<DataState<Unit>>

fun setRecipientAddress(address: Address?)
fun setRecipientAddressWithError(address: Address?, error: Throwable?) = Unit
fun setRecipientAmount(amount: BigDecimal)
fun updateRecipientError(error: Throwable?)
}

class RecipientAddressViewModel(private val service: IRecipientAddressService) : ViewModel() {
Expand Down Expand Up @@ -50,7 +50,11 @@ class RecipientAddressViewModel(private val service: IRecipientAddressService) :
service.setRecipientAddress(address)
}

fun setAddressWithError(address: Address?, error: Throwable?) {
service.setRecipientAddressWithError(address, error)
fun onChangeAddress(address: Address?) {
service.setRecipientAddress(address)
}

fun updateError(error: Throwable?) {
service.updateRecipientError(error)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ class OneInchSettingsService(
sync()
}

override fun setRecipientAddressWithError(address: Address?, error: Throwable?) {
override fun updateRecipientError(error: Throwable?) {
recipientError = error
recipient = address
sync()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ fun RecipientAddress(
tokenQuery = token.tokenQuery,
coinCode = token.coin.code,
navController = navController,
onStateChange = {
recipientAddressViewModel.setAddressWithError(
it?.dataOrNull,
it?.errorOrNull
)
}
onError = {
recipientAddressViewModel.updateError(it)
},
onValueChange = {
recipientAddressViewModel.onChangeAddress(it)
},
)
InfoText(
text = stringResource(R.string.SwapSettings_RecipientAddressDescription),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,8 @@ class UniswapSettingsService(
sync()
}

override fun setRecipientAddressWithError(address: Address?, error: Throwable?) {
override fun updateRecipientError(error: Throwable?) {
recipientError = error
recipient = address
sync()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class WithdrawCexViewModel(

var coinRate by mutableStateOf(coinUid?.let { xRateService.getRate(it) })
private set
var value by mutableStateOf("")
private set

private var amountState = amountService.stateFlow.value
private var addressState = addressService.stateFlow.value
Expand Down Expand Up @@ -131,6 +133,7 @@ class WithdrawCexViewModel(
}

fun onEnterAddress(v: String) {
value = v
viewModelScope.launch {
addressService.setAddress(v)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,10 @@ fun WithdrawCexScreen(
VSpacer(12.dp)
FormsInputAddress(
modifier = Modifier.padding(horizontal = 16.dp),
initial = null,
value = mainViewModel.value,
hint = stringResource(id = R.string.Watch_Address_Hint),
state = uiState.addressState,
textPreprocessor = TextPreprocessorImpl,
onChangeFocus = {
//isFocused = it
},
navController = fragmentNavController,
chooseContactEnable = mainViewModel.hasContacts(),
blockchainType = mainViewModel.blockchainType,
Expand Down
Loading

0 comments on commit 2f8f45b

Please sign in to comment.