Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-23 17:03:53 +03:00
parent 2f2a8b04b5
commit 09b489b175
6 changed files with 100 additions and 69 deletions

View file

@ -13,8 +13,8 @@ import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import arrow.core.getOrElse
@ -54,14 +54,13 @@ import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentSendBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@ -73,6 +72,7 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L
/**
[REDACTED_AUTHOR]
*/
@Suppress("LargeClass")
@OptIn(FlowPreview::class)
@AndroidEntryPoint
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
@ -103,18 +103,25 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lifecycle.addObserver(viewModel)
sendSubscriber.initViewModel(viewModel)
Analytics.send(Token.Send.ScreenOpened())
listenToQrCode()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
subscribeToQrCodeScanner()
subscribeToTransactionExtrasFields()
subscribeToAddressField()
subscribeToAmountField()
}
}
addBackPressHandler(this)
etAmountToSend = view.findViewById(R.id.etAmountToSend)
initSendButtonStates()
setupAddressLayout()
setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
setupWarningMessages()
@ -149,14 +156,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setOnFocusChangeListener { _, hasFocus ->
store.dispatch(TruncateOrRestore(!hasFocus))
}
inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
.filter { store.state.sendState.addressState.viewFieldValue.value != it }
.onEach {
store.dispatch(AddressActionUi.HandleUserInput(it))
}
.launchIn(mainScope)
}
imvPaste.setOnClickListener {
@ -183,31 +182,41 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
private fun listenToQrCode() {
lifecycleScope.launch {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }
.flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED)
.collect { rawQr ->
delay(200)
// 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
cryptoCurrency?.let { cryptoCurrency ->
parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold(
ifLeft = {
onCodeScanned(QrResult(address = rawQr))
Timber.w(it)
},
ifRight = { onCodeScanned(it) },
)
} ?: onCodeScanned(QrResult(address = rawQr))
}
}
private fun CoroutineScope.subscribeToAddressField() = with(binding.lSendAddress) {
etAddress.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
.filter { store.state.sendState.addressState.viewFieldValue.value != it }
.onEach {
store.dispatch(AddressActionUi.HandleUserInput(it))
}
.launchIn(this@subscribeToAddressField)
}
private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) {
private fun CoroutineScope.subscribeToAmountField() {
etAmountToSend.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
.filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
.onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
.launchIn(this)
}
private fun CoroutineScope.subscribeToQrCodeScanner() {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }
.onEach { rawQr ->
cryptoCurrency?.let { cryptoCurrency ->
parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold(
ifLeft = {
onCodeScanned(QrResult(address = rawQr))
Timber.w(it)
},
ifRight = { onCodeScanned(it) },
)
} ?: onCodeScanned(QrResult(address = rawQr))
}.launchIn(this)
}
private fun CoroutineScope.subscribeToTransactionExtrasFields() = with(binding.lSendAddress) {
// TODO: [REDACTED_TASK_KEY]
etXlmMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -216,7 +225,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.xlmMemo?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etDestinationTag.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -225,7 +234,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.xrpDestinationTag?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etBinanceMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -234,7 +243,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.binanceMemo?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etTonMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -243,7 +252,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.tonMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etCosmosMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -252,7 +261,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.cosmosMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etHederaMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -261,7 +270,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.hederaMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.HederaMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
etAlgorandMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@ -270,7 +279,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.algorandMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.AlgorandMemo.HandleUserInput(it)) }
.launchIn(mainScope)
.launchIn(this@subscribeToTransactionExtrasFields)
}
private fun onCodeScanned(parsedQr: QrResult) {
@ -343,12 +352,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0")
}
etAmountToSend.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
.filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
.onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
.launchIn(mainScope)
etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) {
it.hideSoftKeyboard()
it.clearFocus()