Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-11 20:50:18 +03:00
parent 13fcfb8728
commit b723940318
16 changed files with 182 additions and 22 deletions

View file

@ -7,6 +7,8 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.qrscanning.SourceType
import com.tangem.features.send.impl.presentation.SendFragment
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -31,4 +33,16 @@ internal class DefaultSendRouter(
),
)
}
override fun openQrCodeScanner(network: String) {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.QrScanning,
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.SEND,
QrScanningRouter.NETWORK_KEY to network,
),
),
)
}
}

View file

@ -11,4 +11,7 @@ interface InnerSendRouter : SendRouter {
/** Open token details screen by [userWalletId] and [currency] */
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
/** Open QR code scanner screen */
fun openQrCodeScanner(network: String)
}

View file

@ -4,16 +4,25 @@ import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.feature.qrscanning.SourceType
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.navigation.InnerSendRouter
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.ui.SendScreen
import com.tangem.features.send.impl.presentation.viewmodel.SendViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -29,6 +38,9 @@ internal class SendFragment : ComposeFragment() {
@Inject
lateinit var router: SendRouter
@Inject
lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase
private val viewModel by viewModels<SendViewModel>()
private val innerSendRouter: InnerSendRouter
get() = requireNotNull(router as? InnerSendRouter) {
@ -44,6 +56,7 @@ internal class SendFragment : ComposeFragment() {
fragmentManager = WeakReference(parentFragmentManager),
),
)
listenToQrCode()
}
@Composable
@ -60,7 +73,24 @@ internal class SendFragment : ComposeFragment() {
super.onDestroy()
}
private fun listenToQrCode() {
lifecycleScope.launch {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }
.flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED)
.collect {
delay(QR_SCAN_DELAY)
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
// If do not use the delay, then etAmount error field is not displayed when
// inserting an incorrect amount by shareUri
viewModel.onRecipientAddressScanned(it)
}
}
}
companion object {
private const val QR_SCAN_DELAY = 200L
/** Create send fragment instance */
fun create(): SendFragment = SendFragment()

View file

@ -94,11 +94,14 @@ internal class SendStateFactory(
currentState = MutableStateFlow(SendUiStateType.Amount),
)
fun getReadyState(): SendUiState = currentStateProvider().copy(
amountState = amountStateConverter.convert(Unit),
recipientState = recipientStateConverter.convert(Unit),
feeState = feeStateConverter.convert(Unit),
)
fun getReadyState(): SendUiState {
val state = currentStateProvider()
return state.copy(
amountState = state.amountState ?: amountStateConverter.convert(Unit),
recipientState = state.recipientState ?: recipientStateConverter.convert(Unit),
feeState = state.feeState ?: feeStateConverter.convert(Unit),
)
}
//endregion
//region amount state clicks
@ -160,9 +163,8 @@ internal class SendStateFactory(
isValidating = false,
addressTextField = recipientState.addressTextField.copy(
error = when {
!isValidAddress || isAddressInWallet -> resourceReference(
R.string.send_recipient_address_error,
)
!isValidAddress -> resourceReference(R.string.send_recipient_address_error)
isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet)
else -> null
},
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,

View file

@ -6,7 +6,6 @@ import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
internal class SendAmountFieldChangeConverter(
@ -106,8 +105,7 @@ internal class SendAmountFieldChangeConverter(
var trimmedValue = this
if (length > 1 && firstOrNull() == '0' && get(1).isDigit()) trimmedValue = drop(1)
val separatorChar = DecimalFormatSymbols.getInstance().decimalSeparator.toString()
return trimmedValue.replace(TRIM_REGEX.toRegex(), separatorChar)
return trimmedValue.replace(TRIM_REGEX.toRegex(), ".")
}
companion object {

View file

@ -26,14 +26,15 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.ValidateWalletAddressUseCase
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.navigation.InnerSendRouter
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.*
import com.tangem.features.send.impl.presentation.state.SendNotificationFactory
import com.tangem.features.send.impl.presentation.state.SendStateFactory
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.state.fee.getFee
@ -67,6 +68,7 @@ internal class SendViewModel @Inject constructor(
private val sendTransactionUseCase: SendTransactionUseCase,
private val createTransactionUseCase: CreateTransactionUseCase,
private val validateWalletAddressUseCase: ValidateWalletAddressUseCase,
private val parseSharedAddressUseCase: ParseSharedAddressUseCase,
private val walletManagersFacade: WalletManagersFacade,
getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
@ -116,6 +118,7 @@ internal class SendViewModel @Inject constructor(
private var feeJobHolder = JobHolder()
private var addressValidationJobHolder = JobHolder()
private var sendNotificationsJobHolder = JobHolder()
private var qrScannerJobHolder = JobHolder()
override fun onCreate(owner: LifecycleOwner) {
subscribeOnCurrencyStatusUpdates(owner)
@ -326,9 +329,7 @@ internal class SendViewModel @Inject constructor(
override fun onNextClick() = stateRouter.onNextClick()
override fun onPrevClick() = stateRouter.onPrevClick()
override fun onQrCodeScanClick() {
// TODO Add QR code scanning
}
override fun onQrCodeScanClick() = innerRouter.openQrCodeScanner(cryptoCurrency.network.name)
override fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency) =
innerRouter.openTokenDetails(userWalletId, currency)
@ -355,6 +356,21 @@ internal class SendViewModel @Inject constructor(
// endregion
// region recipient state clicks
fun onRecipientAddressScanned(address: String) {
viewModelScope.launch(dispatchers.main) {
parseSharedAddressUseCase(address, cryptoCurrency.network).fold(
ifRight = { parsedCode ->
onRecipientAddressValueChange(parsedCode.address)
parsedCode.amount?.let { onAmountValueChange(it.toPlainString()) }
parsedCode.memo?.let { onRecipientMemoValueChange(it) }
},
ifLeft = {
Timber.w(it)
},
)
}.saveIn(qrScannerJobHolder)
}
override fun onRecipientAddressValueChange(value: String) {
uiState = stateFactory.onRecipientAddressValueChange(value)
viewModelScope.launch(dispatchers.main) {