Updated on 2026-08-14
This commit is contained in:
parent
fd7bc522f2
commit
194e750c93
74 changed files with 52 additions and 4627 deletions
|
|
@ -1,44 +1,22 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
||||
object DemoHelper {
|
||||
val config = DemoConfig()
|
||||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
)
|
||||
|
||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||
|
||||
fun isTestDemoCard(scanResponse: ScanResponse): Boolean = config.isTestDemoCardId(scanResponse.card.cardId)
|
||||
|
||||
fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
|
||||
|
||||
fun tryHandle(appState: () -> AppState?, action: Action): Boolean {
|
||||
fun tryHandle(appState: () -> AppState?): Boolean {
|
||||
val scanResponse = getScanResponse(appState) ?: return false
|
||||
if (!scanResponse.isDemoCard()) return false
|
||||
|
||||
disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
|
||||
val uiMessageSender = store.inject(DaggerGraphState::uiMessageSender)
|
||||
uiMessageSender.send(
|
||||
DialogMessage(
|
||||
message = resourceReference(R.string.alert_demo_feature_disabled),
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class DetailsMiddleware {
|
|||
val detailsMiddleware: Middleware<AppState> = { _, stateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (!DemoHelper.tryHandle(stateProvider, action)) {
|
||||
if (!DemoHelper.tryHandle(stateProvider)) {
|
||||
val detailsState = stateProvider()?.detailsState
|
||||
if (detailsState != null) {
|
||||
handleAction(detailsState, action)
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class WalletConnectAction : Action {
|
||||
data class HandleDeepLink(val wcUri: String?) : WalletConnectAction()
|
||||
|
||||
data object StartWalletConnect : WalletConnectAction()
|
||||
|
||||
data class OpenSession(
|
||||
val wcUri: String,
|
||||
val source: SourceType,
|
||||
val userWalletId: UserWalletId,
|
||||
) : WalletConnectAction() {
|
||||
enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC }
|
||||
}
|
||||
|
||||
data class DisconnectSession(val topic: String) : WalletConnectAction()
|
||||
|
||||
data class RejectRequest(val topic: String, val id: Long) : WalletConnectAction()
|
||||
|
||||
//region WalletConnect 2.0
|
||||
data class ApproveProposal(val proposal: WalletConnectEvents.SessionProposal) : WalletConnectAction()
|
||||
data object RejectProposal : WalletConnectAction()
|
||||
|
||||
data object SessionEstablished : WalletConnectAction()
|
||||
data class SessionRejected(val error: WalletConnectError) : WalletConnectAction()
|
||||
data class SessionListUpdated(val sessions: List<WcSessionForScreen>) : WalletConnectAction()
|
||||
|
||||
data class ShowSessionRequest(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
|
||||
data object RejectUnsupportedRequest : WalletConnectAction()
|
||||
|
||||
data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
|
||||
data class PairConnectErrorAction(val throwable: Throwable) : WalletConnectAction()
|
||||
|
||||
data object UnsupportedDappRequest : WalletConnectAction()
|
||||
//endregion WalletConnect 2.0
|
||||
}
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletConnectMiddleware {
|
||||
private val walletConnectInteractor: WalletConnectInteractor
|
||||
get() = store.inject(DaggerGraphState::walletConnectInteractor)
|
||||
private val walletConnectRepository: LegacyWalletConnectRepository
|
||||
get() = store.inject(DaggerGraphState::walletConnectRepository)
|
||||
|
||||
val walletConnectMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handle(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
private fun handle(state: () -> AppState?, action: Action) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
when (action) {
|
||||
is WalletConnectAction.HandleDeepLink -> {
|
||||
val wsUrl = action.wcUri
|
||||
Timber.i("WC deeplink: $wsUrl")
|
||||
if (!wsUrl.isNullOrBlank()) {
|
||||
Timber.i("WC deeplink added to stack: $wsUrl")
|
||||
walletConnectInteractor.addDeeplink(wsUrl)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.DisconnectSession -> {
|
||||
walletConnectInteractor.disconnectSession(action.topic)
|
||||
}
|
||||
is WalletConnectAction.StartWalletConnect -> {
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.QrScanning(AppRoute.QrScanning.Source.WalletConnect))
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
val index = action.wcUri.indexOf("@")
|
||||
when (action.wcUri[index + 1]) {
|
||||
'2' -> walletConnectRepository.pair(
|
||||
uri = action.wcUri,
|
||||
source = action.source,
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
'1' -> {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedWcVersion),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.RejectRequest -> {
|
||||
walletConnectInteractor.cancelRequest(action.topic, action.id)
|
||||
}
|
||||
is WalletConnectAction.ApproveProposal -> {
|
||||
scope.launch {
|
||||
val proposalChainIds = action.proposal.requiredChainIds + action.proposal.optionalChainIds
|
||||
val proposalBlockchains =
|
||||
walletConnectInteractor.blockchainHelper.chainIdsToBlockchains(proposalChainIds)
|
||||
val accounts = getWalletManagers()
|
||||
.filter { walletManager -> proposalBlockchains.contains(walletManager.wallet.blockchain) }
|
||||
.flatMap {
|
||||
val wallet = it.wallet
|
||||
val chainIds = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
walletConnectInteractor.blockchainHelper.chainIdsToAccounts(
|
||||
walletAddress = wallet.address,
|
||||
chainIds = chainIds,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
walletConnectInteractor.approveSessionProposal(accounts)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.RejectProposal -> {
|
||||
walletConnectInteractor.rejectSessionProposal()
|
||||
}
|
||||
is WalletConnectAction.SessionEstablished -> {
|
||||
}
|
||||
is WalletConnectAction.SessionRejected -> {
|
||||
when (action.error) {
|
||||
is WalletConnectError.ApprovalErrorAddNetwork -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.AddNetwork(action.error.networks),
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.ApprovalErrorUnsupportedNetwork -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.UnsupportedNetwork(action.error.unsupportedNetworks),
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.UnsupportedDApp -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.UnsupportedDapp,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.UnknownError -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.wallet_connect_title,
|
||||
messageId = R.string.wallet_connect_error_with_framework_message,
|
||||
args = listOf(action.error.message),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.ExternalApprovalError -> {
|
||||
Timber.e(action.error, "ExternalApprovalError ${action.error.message}")
|
||||
// do not show dialog on this event
|
||||
// val message = action.error.message
|
||||
// if (!message.isNullOrEmpty()) {
|
||||
// store.dispatchOnMain(
|
||||
// GlobalAction.ShowDialog(
|
||||
// AppDialog.SimpleOkWarningDialog(
|
||||
// message = message,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.ShowSessionRequest -> {
|
||||
val dialog: WalletConnectDialog = when (val request = action.sessionRequest) {
|
||||
is WcPreparedRequest.BnbTransaction -> WalletConnectDialog.BnbTransactionDialog(request)
|
||||
is WcPreparedRequest.EthTransaction -> WalletConnectDialog.RequestTransaction(request)
|
||||
is WcPreparedRequest.EthSign -> WalletConnectDialog.PersonalSign(request)
|
||||
is WcPreparedRequest.SolanaSignTransaction ->
|
||||
WalletConnectDialog.SignTransactionDialog(request)
|
||||
is WcPreparedRequest.SolanaSignMultipleTransactions ->
|
||||
WalletConnectDialog.SignTransactionsDialog(request)
|
||||
}
|
||||
store.dispatch(GlobalAction.ShowDialog(dialog))
|
||||
}
|
||||
is WalletConnectAction.PerformRequestedAction -> {
|
||||
scope.launch { walletConnectInteractor.continueWithRequest(action.sessionRequest) }
|
||||
}
|
||||
is WalletConnectAction.RejectUnsupportedRequest -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
|
||||
}
|
||||
is WalletConnectAction.PairConnectErrorAction -> {
|
||||
store.dispatch(GlobalAction.ShowDialog(WalletConnectDialog.PairConnectErrorDialog(action.throwable)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWalletManagers(): List<WalletManager> {
|
||||
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = getSelectedWallet() ?: return emptyList()
|
||||
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
}
|
||||
|
||||
private suspend fun getSelectedWallet(): UserWallet? {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
|
||||
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.selectedUserWalletSync()
|
||||
} else {
|
||||
userWalletsListManager.selectedUserWalletSync
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
object WalletConnectReducer {
|
||||
fun reduce(action: Action, state: WalletConnectState): WalletConnectState {
|
||||
if (action !is WalletConnectAction) return state
|
||||
|
||||
return when (action) {
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
state.copy(loading = true)
|
||||
}
|
||||
is WalletConnectAction.ApproveProposal -> state.copy(loading = true)
|
||||
is WalletConnectAction.RejectProposal,
|
||||
is WalletConnectAction.SessionEstablished,
|
||||
is WalletConnectAction.SessionRejected,
|
||||
is WalletConnectAction.PairConnectErrorAction,
|
||||
is WalletConnectAction.UnsupportedDappRequest,
|
||||
-> state.copy(loading = false)
|
||||
is WalletConnectAction.SessionListUpdated -> state.copy(
|
||||
wc2Sessions = action.sessions,
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
|
||||
data class WalletConnectState(
|
||||
val loading: Boolean = false,
|
||||
val wc2Sessions: List<WcSessionForScreen> = listOf(),
|
||||
val newSessionData: NewWcSessionData? = null,
|
||||
)
|
||||
|
||||
data class NewWcSessionData(
|
||||
val session: WalletConnectSession,
|
||||
val scanResponse: ScanResponse,
|
||||
val blockchain: Blockchain?,
|
||||
)
|
||||
|
||||
data class WalletConnectSession(
|
||||
val peerId: String,
|
||||
val remotePeerId: String?,
|
||||
val wallet: WalletForSession,
|
||||
) {
|
||||
fun getAddress(): String? {
|
||||
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null
|
||||
return wallet.blockchain?.makeAddresses(key)?.first()?.value
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletForSession(
|
||||
val walletPublicKey: ByteArray? = null,
|
||||
val derivedPublicKey: ByteArray? = null,
|
||||
val derivationPath: DerivationPath? = null,
|
||||
val derivationStyle: DerivationStyle? = null,
|
||||
val isTestNet: Boolean = false,
|
||||
val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum,
|
||||
) {
|
||||
|
||||
fun getBlockchainForSession(): Blockchain {
|
||||
return blockchain ?: if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as WalletForSession
|
||||
|
||||
if (walletPublicKey != null) {
|
||||
if (other.walletPublicKey == null) return false
|
||||
if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false
|
||||
} else if (other.walletPublicKey != null) return false
|
||||
if (derivedPublicKey != null) {
|
||||
if (other.derivedPublicKey == null) return false
|
||||
if (!derivedPublicKey.contentEquals(other.derivedPublicKey)) return false
|
||||
} else if (other.derivedPublicKey != null) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
if (isTestNet != other.isTestNet) return false
|
||||
return blockchain == other.blockchain
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = walletPublicKey?.contentHashCode() ?: 0
|
||||
result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (derivationPath?.hashCode() ?: 0)
|
||||
result = 31 * result + isTestNet.hashCode()
|
||||
result = 31 * result + (blockchain?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletConnectDialog : StateDialog {
|
||||
data object UnsupportedWcVersion : WalletConnectDialog()
|
||||
data object UnsupportedCard : WalletConnectDialog()
|
||||
data class UnsupportedNetwork(val networks: List<String>? = null) : WalletConnectDialog()
|
||||
data object UnsupportedDapp : WalletConnectDialog()
|
||||
data class AddNetwork(val networks: List<String>) : WalletConnectDialog()
|
||||
data object OpeningSessionRejected : WalletConnectDialog()
|
||||
data object SessionTimeout : WalletConnectDialog()
|
||||
data class ApproveWcSession(
|
||||
val session: WalletConnectSession,
|
||||
val networks: List<Blockchain>,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class SessionProposalDialog(
|
||||
val sessionProposal: WalletConnectEvents.SessionProposal,
|
||||
val networks: String,
|
||||
val onApprove: () -> Unit,
|
||||
val onReject: () -> Unit,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class ChooseNetwork(
|
||||
val session: WalletConnectSession,
|
||||
val networks: List<Blockchain>,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class RequestTransaction(val data: WcPreparedRequest.EthTransaction) :
|
||||
WalletConnectDialog()
|
||||
|
||||
data class PersonalSign(val data: WcPreparedRequest.EthSign) : WalletConnectDialog()
|
||||
data class BnbTransactionDialog(
|
||||
val data: WcPreparedRequest.BnbTransaction,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class SignTransactionDialog(
|
||||
val data: WcPreparedRequest.SolanaSignTransaction,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class SignTransactionsDialog(
|
||||
val data: WcPreparedRequest.SolanaSignMultipleTransactions,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class PairConnectErrorDialog(val error: Throwable) : WalletConnectDialog()
|
||||
}
|
||||
|
||||
data class WcTransactionData(
|
||||
val type: WcEthTransactionType,
|
||||
val transaction: TransactionData,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val walletManager: WalletManager,
|
||||
val dialogData: TransactionRequestDialogData,
|
||||
)
|
||||
|
||||
enum class WcEthTransactionType {
|
||||
EthSignTransaction,
|
||||
EthSendTransaction,
|
||||
}
|
||||
|
||||
data class WcPersonalSignData(
|
||||
val hash: ByteArray,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val dialogData: PersonalSignDialogData,
|
||||
val type: WcSignMessage.WCSignType,
|
||||
)
|
||||
|
||||
sealed class BinanceMessageData(
|
||||
val address: String,
|
||||
val data: ByteArray,
|
||||
) {
|
||||
class Trade(
|
||||
val tradeData: List<TradeData>,
|
||||
address: String,
|
||||
data: ByteArray,
|
||||
) : BinanceMessageData(address, data)
|
||||
|
||||
class Transfer(
|
||||
val outputAddress: String,
|
||||
val amount: String,
|
||||
address: String,
|
||||
data: ByteArray,
|
||||
) : BinanceMessageData(address, data)
|
||||
}
|
||||
|
||||
data class TradeData(
|
||||
val price: String,
|
||||
val quantity: String,
|
||||
val amount: String,
|
||||
val symbol: String,
|
||||
)
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.store
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: WalletConnectComponent.Params,
|
||||
) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber<WalletConnectState> {
|
||||
|
||||
private val model: WalletConnectModel = getOrCreateModel(params)
|
||||
|
||||
private var screenState: MutableState<WalletConnectScreenState> =
|
||||
mutableStateOf(model.updateState(store.state.walletConnectState))
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
Analytics.send(WalletConnect.ScreenOpened())
|
||||
},
|
||||
onStart = {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.walletConnectState == newState.walletConnectState
|
||||
}.select { it.walletConnectState }
|
||||
}
|
||||
},
|
||||
onStop = {
|
||||
store.unsubscribe(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun newState(state: WalletConnectState) {
|
||||
screenState.value = model.updateState(state)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
WalletConnectScreen(
|
||||
modifier = modifier,
|
||||
state = screenState.value,
|
||||
onBackClick = {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : WalletConnectComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: WalletConnectComponent.Params,
|
||||
): DefaultWalletConnectComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class WalletConnectModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<WalletConnectComponent.Params>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
listenToQrScanningUseCase.listen(SourceType.WALLET_CONNECT)
|
||||
.getOrElse { emptyFlow() }
|
||||
.map { result ->
|
||||
val source = when (result.resultSource) {
|
||||
QrResultSource.CLIPBOARD -> WalletConnectAction.OpenSession.SourceType.CLIPBOARD
|
||||
QrResultSource.CAMERA,
|
||||
QrResultSource.GALLERY,
|
||||
-> WalletConnectAction.OpenSession.SourceType.QR
|
||||
}
|
||||
WalletConnectAction.OpenSession(
|
||||
wcUri = result.qrCode,
|
||||
source = source,
|
||||
userWalletId = params.userWalletId,
|
||||
)
|
||||
}
|
||||
.collect { store.dispatch(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun updateState(state: WalletConnectState): WalletConnectScreenState {
|
||||
Timber.d("WC2 Sessions: ${state.wc2Sessions}")
|
||||
val sessions = state.wc2Sessions
|
||||
return WalletConnectScreenState(
|
||||
sessions.toImmutableList(),
|
||||
isLoading = state.loading,
|
||||
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) },
|
||||
onAddSession = {
|
||||
store.dispatch(WalletConnectAction.StartWalletConnect)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun onRemoveSession(sessionUri: String, wc2sessions: List<WcSessionForScreen>) {
|
||||
wc2sessions.firstOrNull { it.sessionId == sessionUri }?.let {
|
||||
store.dispatch(WalletConnectAction.DisconnectSession(sessionUri))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun WalletConnectScreen(
|
||||
state: WalletConnectScreenState,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SettingsScreensScaffold(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
if (state.sessions.isEmpty()) {
|
||||
EmptyScreen(state)
|
||||
} else {
|
||||
WalletConnectSessions(state)
|
||||
}
|
||||
},
|
||||
fab = {
|
||||
if (!state.isLoading) {
|
||||
AddSessionFab(
|
||||
onAddSession = {
|
||||
Analytics.send(Settings.ButtonStartWalletConnectSession())
|
||||
state.onAddSession()
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddSessionFab(onAddSession: () -> Unit, modifier: Modifier = Modifier) {
|
||||
FloatingActionButton(
|
||||
onClick = onAddSession,
|
||||
containerColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.icon.primary2,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_plus_24),
|
||||
contentDescription = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyScreen(state: WalletConnectScreenState) {
|
||||
if (state.isLoading) {
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = 64.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_wallet_connect_24),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.width(width = 100.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.wallet_connect_subtitle),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
||||
if (state.isLoading) {
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(2.dp),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
items(state.sessions) { session ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = session.description,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
Analytics.send(Settings.ButtonStopWalletConnectSession())
|
||||
state.onRemoveSession(session.sessionId)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_cross_rounded_24),
|
||||
contentDescription = "",
|
||||
tint = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun WalletConnectScreenPreview() {
|
||||
WalletConnectScreen(
|
||||
state = WalletConnectScreenState(
|
||||
sessions = persistentListOf(
|
||||
WcSessionForScreen(
|
||||
description = "session from some dApp",
|
||||
sessionId = "",
|
||||
),
|
||||
),
|
||||
isLoading = true,
|
||||
),
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class WalletConnectScreenState(
|
||||
val sessions: ImmutableList<WcSessionForScreen>,
|
||||
val isLoading: Boolean = false,
|
||||
val onRemoveSession: (String) -> Unit = {},
|
||||
val onAddSession: () -> Unit = {},
|
||||
)
|
||||
|
||||
data class WcSessionForScreen(
|
||||
val description: String,
|
||||
val sessionId: String,
|
||||
)
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface WalletConnectComponent : ComposableContentComponent {
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
interface Factory : ComponentFactory<Params, WalletConnectComponent>
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.tap.features.details.ui.walletconnect.DefaultWalletConnectComponent
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectModel
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface WalletConnectFeatureModule {
|
||||
|
||||
@Binds
|
||||
fun bindFactory(impl: DefaultWalletConnectComponent.Factory): WalletConnectComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(WalletConnectModel::class)
|
||||
fun bindModel(model: WalletConnectModel): Model
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object BnbTransactionDialog {
|
||||
fun create(preparedData: WcPreparedRequest.BnbTransaction, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.data
|
||||
val message = when (data) {
|
||||
is BinanceMessageData.Trade -> data.tradeData.map {
|
||||
context.getString(
|
||||
R.string.wallet_connect_bnb_trade_order_message,
|
||||
it.symbol,
|
||||
it.price,
|
||||
it.quantity,
|
||||
it.amount,
|
||||
)
|
||||
}.joinToString(separator = "\n\n")
|
||||
is BinanceMessageData.Transfer -> context.getString(
|
||||
R.string.wallet_connect_bnb_transaction_message,
|
||||
data.address,
|
||||
data.outputAddress,
|
||||
data.amount,
|
||||
)
|
||||
}
|
||||
|
||||
val fullMessage = context.getString(
|
||||
R.string.wallet_connect_bnb_sign_message,
|
||||
preparedData.preparedRequestData.dAppName,
|
||||
message,
|
||||
)
|
||||
val positiveButtonTitle = context.getText(R.string.common_sign)
|
||||
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(fullMessage)
|
||||
setPositiveButton(positiveButtonTitle) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object PersonalSignDialog {
|
||||
fun create(preparedData: WcPreparedRequest.EthSign, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.dialogData
|
||||
val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message)
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
||||
data class PersonalSignDialogData(
|
||||
val dAppName: String,
|
||||
val message: String,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
)
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object SessionProposalDialog {
|
||||
fun create(
|
||||
sessionProposal: WalletConnectEvents.SessionProposal,
|
||||
networks: String,
|
||||
context: Context,
|
||||
onApprove: () -> Unit,
|
||||
onReject: () -> Unit,
|
||||
): AlertDialog {
|
||||
val message = context.getString(
|
||||
R.string.wallet_connect_request_session_start,
|
||||
sessionProposal.name,
|
||||
networks,
|
||||
sessionProposal.url,
|
||||
)
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_start)) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onApprove()
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onReject()
|
||||
}
|
||||
setOnCancelListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onReject()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal object SignTransactionDialog {
|
||||
|
||||
fun create(preparedData: WcPreparedRequest.SolanaSignTransaction, context: Context): AlertDialog {
|
||||
val signMessage = context.getString(R.string.wallet_connect_alert_sign_message, "")
|
||||
val message = "${preparedData.preparedRequestData.dAppName}\n$signMessage"
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal object SignTransactionsDialog {
|
||||
|
||||
fun create(preparedData: WcPreparedRequest.SolanaSignMultipleTransactions, context: Context): AlertDialog {
|
||||
val signMessage = context.getString(R.string.wallet_connect_alert_sign_message, "")
|
||||
val message = "${preparedData.preparedRequestData.dAppName}\n$signMessage"
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object TransactionDialog {
|
||||
fun create(preparedData: WcPreparedRequest.EthTransaction, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.dialogData
|
||||
val message = context.getString(
|
||||
R.string.wallet_connect_create_tx_message,
|
||||
data.dAppName,
|
||||
data.dAppUrl,
|
||||
data.amount,
|
||||
data.feeAmount,
|
||||
data.totalAmount,
|
||||
data.balance,
|
||||
)
|
||||
|
||||
val positiveButtonTitle = when (data.type) {
|
||||
WcEthTransactionType.EthSignTransaction -> context.getText(R.string.common_sign)
|
||||
WcEthTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send)
|
||||
}
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(positiveButtonTitle) { _, _ ->
|
||||
if (data.isEnoughFundsToSend) {
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
} else {
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
}
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
||||
data class TransactionRequestDialogData(
|
||||
val dAppName: String,
|
||||
val dAppUrl: String,
|
||||
val amount: String,
|
||||
val feeAmount: String,
|
||||
val totalAmount: String,
|
||||
val balance: String,
|
||||
val isEnoughFundsToSend: Boolean,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val type: WcEthTransactionType,
|
||||
)
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletConnectLinkIntentHandler : IntentHandler, AffectsNavigation {
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
val intentData = intent?.data ?: return false
|
||||
val scheme = intent.scheme ?: return false
|
||||
|
||||
val wcUri = when (scheme) {
|
||||
WC_SCHEME -> intentData.toString()
|
||||
TANGEM_SCHEME -> intentData.toString().removePrefixOrNull(TANGEM_WC_PREFIX)
|
||||
else -> null
|
||||
}
|
||||
|
||||
return if (wcUri.isNullOrBlank()) {
|
||||
false
|
||||
} else {
|
||||
val decodedWcUri = try {
|
||||
URLDecoder.decode(wcUri, DEFAULT_CHARSET_NAME)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
store.dispatchOnMain(WalletConnectAction.HandleDeepLink(decodedWcUri))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TANGEM_SCHEME = "tangem"
|
||||
const val TANGEM_WC_PREFIX = "tangem://wc?uri="
|
||||
const val DEFAULT_CHARSET_NAME = "UTF-8"
|
||||
const val WC_SCHEME = "wc"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,15 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Onboarding.*
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.analytics.events.Onboarding.Finished
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
|
|
@ -24,7 +27,7 @@ class BackupMiddleware {
|
|||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
if (DemoHelper.tryHandle(appState)) return
|
||||
|
||||
when (action) {
|
||||
is BackupAction.DiscardBackup -> {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ object TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
if (DemoHelper.tryHandle(state)) return
|
||||
|
||||
when (action) {
|
||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.domain.wallets.legacy.unlockIfLockable
|
|||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -32,21 +31,19 @@ internal class WelcomeMiddleware {
|
|||
{ action ->
|
||||
val appState = appStateProvider()
|
||||
if (action is WelcomeAction && appState != null) {
|
||||
handleAction(action, appState.welcomeState)
|
||||
handleAction(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||
private fun handleAction(action: WelcomeAction) {
|
||||
mainScope.launch {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(
|
||||
afterUnlockIntent = action.afterUnlockIntent ?: state.intent,
|
||||
)
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard(afterScanIntent = state.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics()
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard()
|
||||
is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving()
|
||||
else -> Unit
|
||||
}
|
||||
|
|
@ -70,13 +67,8 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithBiometrics(afterUnlockIntent: Intent?) {
|
||||
Timber.d(
|
||||
"""
|
||||
Proceeding with biometry
|
||||
|- Intent: $afterUnlockIntent
|
||||
""".trimIndent(),
|
||||
)
|
||||
private suspend fun proceedWithBiometrics() {
|
||||
Timber.d("Proceeding with biometry")
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.unlockIfLockable(type = UnlockType.ANY)
|
||||
|
|
@ -93,20 +85,11 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
|
||||
afterUnlockIntent?.let {
|
||||
WalletConnectLinkIntentHandler().handleIntent(it, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithCard(afterScanIntent: Intent?) {
|
||||
Timber.d(
|
||||
"""
|
||||
Proceeding with card
|
||||
|- Intent: $afterScanIntent
|
||||
""".trimIndent(),
|
||||
)
|
||||
private suspend fun proceedWithCard() {
|
||||
Timber.d("Proceeding with card")
|
||||
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse)
|
||||
|
|
@ -125,10 +108,6 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
|
||||
afterScanIntent?.let {
|
||||
WalletConnectLinkIntentHandler().handleIntent(it, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue