diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1634169316..efe2848d8c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,6 +39,8 @@ dependencies { /** Features */ implementation(project(":features:onboarding")) + implementation(project(":features:learn2earn:api")) + implementation(project(":features:learn2earn:impl")) implementation(project(":features:referral:presentation")) implementation(project(":features:referral:domain")) implementation(project(":features:referral:data")) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ef93ea2179..7185ff029d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,10 @@ android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity" android:theme="@style/AppTheme" /> + + { - navigateToInitialScreen(intent) + navigateToInitialScreenOnResume(intentWhichStartedActivity) } backStackIsEmpty -> { - navigateToInitialScreen(intent) + navigateToInitialScreenOnResume(intentWhichStartedActivity) } else -> Unit } } - private fun navigateToInitialScreen(intent: Intent?) { + private fun navigateToInitialScreenOnResume(intentWhichStartedActivity: Intent?) { if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) - store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent)) + store.dispatchOnMain(WelcomeAction.SetInitialIntent(intentWhichStartedActivity)) + scope.launch { + val handler = BackgroundScanIntentHandler(hasSavedUserWalletsProvider = { true }) + val isBackgroundScanNotHandled = handler.handleIntent(intentWhichStartedActivity) + val hasNotIncompletedBackup = !backupService.hasIncompletedBackup + if (isBackgroundScanNotHandled && hasNotIncompletedBackup) { + store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) + } + } } else { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home)) - intentHandler.handleIntent(intent, hasSavedUserWallets = false) + scope.launch { + intentProcessor.handleIntent(intentWhichStartedActivity) + } } store.dispatch(BackupAction.CheckForUnfinishedBackup) } diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 5de365ee79..114746ccdb 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -1,59 +1,67 @@ package com.tangem.tap -import android.app.* -import android.content.* -import android.content.pm.* -import coil.* -import com.tangem.* -import com.tangem.blockchain.common.* -import com.tangem.blockchain.network.* -import com.tangem.core.analytics.* -import com.tangem.core.featuretoggle.manager.* -import com.tangem.data.source.preferences.* -import com.tangem.datasource.api.common.* -import com.tangem.datasource.asset.* -import com.tangem.datasource.config.* -import com.tangem.datasource.config.models.* -import com.tangem.datasource.connection.* -import com.tangem.domain.* -import com.tangem.domain.common.* -import com.tangem.features.wallet.featuretoggles.* -import com.tangem.tap.common.* -import com.tangem.tap.common.analytics.* -import com.tangem.tap.common.analytics.api.* -import com.tangem.tap.common.analytics.handlers.amplitude.* -import com.tangem.tap.common.analytics.handlers.appsFlyer.* -import com.tangem.tap.common.analytics.handlers.firebase.* -import com.tangem.tap.common.analytics.topup.* -import com.tangem.tap.common.chat.* -import com.tangem.tap.common.feedback.* -import com.tangem.tap.common.images.* -import com.tangem.tap.common.log.* -import com.tangem.tap.common.redux.* -import com.tangem.tap.common.redux.global.* -import com.tangem.tap.common.shop.* -import com.tangem.tap.domain.configurable.warningMessage.* -import com.tangem.tap.domain.tokens.* -import com.tangem.tap.domain.totalBalance.* -import com.tangem.tap.domain.totalBalance.di.* -import com.tangem.tap.domain.walletCurrencies.* -import com.tangem.tap.domain.walletCurrencies.di.* -import com.tangem.tap.domain.walletStores.* -import com.tangem.tap.domain.walletStores.di.* -import com.tangem.tap.domain.walletStores.repository.* -import com.tangem.tap.domain.walletStores.repository.di.* +import android.app.Application +import android.content.Context +import android.content.pm.PackageManager +import coil.ImageLoader +import coil.ImageLoaderFactory +import com.tangem.Log +import com.tangem.LogFormat +import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder +import com.tangem.core.analytics.Analytics +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.data.source.preferences.PreferencesDataSource +import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.config.ConfigManager +import com.tangem.datasource.config.FeaturesLocalLoader +import com.tangem.datasource.config.models.Config +import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.DomainLayer +import com.tangem.domain.common.LogConfig +import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import com.tangem.tap.common.analytics.AnalyticsFactory +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder +import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.topup.TopUpController +import com.tangem.tap.common.chat.ChatManager +import com.tangem.tap.common.feedback.AdditionalFeedbackInfo +import com.tangem.tap.common.feedback.FeedbackManager +import com.tangem.tap.common.images.createCoilImageLoader +import com.tangem.tap.common.log.TangemLogCollector +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.appReducer +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.shop.TangemShopService +import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator +import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation +import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager +import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation +import com.tangem.tap.domain.walletStores.WalletStoresManager +import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository -import com.tangem.tap.domain.walletconnect2.domain.* -import com.tangem.tap.features.customtoken.api.featuretoggles.* -import com.tangem.tap.proxy.* -import com.tangem.tap.proxy.redux.* +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles +import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.wallet.BuildConfig -import dagger.hilt.android.* -import kotlinx.coroutines.* -import okhttp3.logging.* -import org.rekotlin.* -import timber.log.* -import javax.inject.* +import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.runBlocking +import okhttp3.logging.HttpLoggingInterceptor +import org.rekotlin.Store +import timber.log.Timber +import javax.inject.Inject import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository lateinit var store: Store @@ -102,7 +110,6 @@ val walletCurrenciesManager by lazy { val totalFiatBalanceCalculator by lazy { TotalFiatBalanceCalculator.provideDefaultImplementation() } -val intentHandler by lazy { IntentHandler() } @HiltAndroidApp class TapApplication : Application(), ImageLoaderFactory { @@ -137,6 +144,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository + @Inject + lateinit var learn2earnInteractor: Learn2earnInteractor + override fun onCreate() { super.onCreate() @@ -190,8 +200,11 @@ class TapApplication : Application(), ImageLoaderFactory { appStateHolder.userTokensRepository = userTokensRepository appStateHolder.walletStoresManager = walletStoresManager - scope.launch { + // TODO: Try to performance and user experience. + // [REDACTED_JIRA] + runBlocking { featureTogglesManager.init() + learn2earnInteractor.init() } initTopUpController() diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt deleted file mode 100644 index 4d50c59679..0000000000 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.tangem.tap.common - -import android.content.Intent -import android.net.Uri -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Build -import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.removePrefixOrNull -import com.tangem.tap.domain.walletconnect.WalletConnectManager -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.welcome.redux.WelcomeAction -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder -import com.tangem.tap.scope -import com.tangem.tap.store -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import timber.log.Timber - -class IntentHandler { - - private val nfcActions = arrayOf( - NfcAdapter.ACTION_NDEF_DISCOVERED, - NfcAdapter.ACTION_TECH_DISCOVERED, - NfcAdapter.ACTION_TAG_DISCOVERED, - ) - - fun handleIntent(intent: Intent?, hasSavedUserWallets: Boolean) { - handleBackgroundScan(intent, hasSavedUserWallets) - handleWalletConnectLink(intent) - handleBuyCurrencyCallback(intent) - handleSellCurrencyCallback(intent) - } - - fun handleWalletConnectLink(intent: Intent?) { - val wcUri = when (intent?.scheme) { - WalletConnectManager.WC_SCHEME -> { - intent.data?.toString() - } - TANGEM_SCHEME -> { - intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX) - } - else -> { - null - } - } - if (wcUri != null) { - store.dispatch(WalletConnectAction.HandleDeepLink(wcUri)) - } - } - - fun handleBackgroundScan(intent: Intent?, hasSavedUserWallets: Boolean): Boolean { - if (intent == null || intent.action !in nfcActions) return false - - val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) - } - if (tag == null) return false - - intent.action = null - if (hasSavedUserWallets) { - // TODO: Remove delay after [REDACTED_JIRA] - scope.launch { - delay(timeMillis = 200) - store.dispatch(WelcomeAction.ProceedWithCard) - } - } else { - store.dispatch(HomeAction.ReadCard()) - } - - return true - } - - private fun handleBuyCurrencyCallback(intent: Intent?) { - val data = intent?.data ?: return - - val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL) - if (data.host == successUri.host && data.authority == successUri.authority) { - val currency = store.state.walletState.selectedCurrency ?: return - val currencyType = AnalyticsParam.CurrencyType.Currency(currency) - Analytics.send(Token.Bought(currencyType)) - } - } - - fun handleSellCurrencyCallback(intent: Intent?) { - try { - val transactionID = - intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return - val currency = - intent.data?.getQueryParameter(CURRENCY_CODE_PARAM) ?: return - val amount = - intent.data?.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return - val destinationAddress = - intent.data?.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) - ?: return - - Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") - - store.dispatch( - WalletAction.TradeCryptoAction.SendCrypto( - currencyId = currency, - amount = amount, - destinationAddress = destinationAddress, - transactionId = transactionID, - ), - ) - } catch (exception: Exception) { - Timber.d("Not MoonPay URL") - } - } - - companion object { - private const val TRANSACTION_ID_PARAM = "transactionId" - private const val CURRENCY_CODE_PARAM = "baseCurrencyCode" - private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" - private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - private const val TANGEM_SCHEME = "tangem" - private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index f4ef3e133f..e5b89b30e5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -31,12 +31,14 @@ sealed class Basic( currency: AnalyticsParam.CardCurrency, batch: String, signInType: SignInType, + walletsCount: String, ) : Basic( event = "Signed in", params = mapOf( AnalyticsParam.CURRENCY to currency.value, AnalyticsParam.BATCH to batch, "Sign in type" to signInType.name, + "Wallets Count" to walletsCount, ), ) { enum class SignInType { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index c4becfb45b..91c1ae14b7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -6,7 +6,6 @@ import com.tangem.tap.common.analytics.topup.TopUpController import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager @@ -20,7 +19,6 @@ data class GlobalState( val onboardingState: OnboardingState = OnboardingState(), val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), - val payIdManager: PayIdManager = PayIdManager(), val configManager: ConfigManager? = null, val warningManager: WarningMessagesManager? = null, val feedbackManager: FeedbackManager? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt deleted file mode 100644 index 11d5300454..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain - -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.services.Result -import com.tangem.tap.network.payid.PayIdVerifyService -import com.tangem.tap.network.payid.VerifyPayIdResponse -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.util.* - -class PayIdManager { - - @Suppress("MagicNumber") - suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = - withContext(Dispatchers.IO) { - val splitPayId = payId.split("\$") - val user = splitPayId[0] - val baseUrl = "https://${splitPayId[1]}/" - return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork()) - } - - private fun Blockchain.getPayIdNetwork(): String { - return when (this) { - Blockchain.XRP -> "XRPL" - Blockchain.RSK -> "RSK" - else -> this.currency - }.lowercase(Locale.getDefault()) - } - - companion object { - private val payIdRegExp = ( - "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" + - "(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$" - ).toRegex() - - val payIdSupported: EnumSet = EnumSet.of( - Blockchain.XRP, - Blockchain.Ethereum, - Blockchain.Bitcoin, - Blockchain.Litecoin, - Blockchain.Stellar, - Blockchain.Cardano, - Blockchain.CardanoShelley, - Blockchain.BitcoinCash, - Blockchain.Binance, - Blockchain.RSK, - Blockchain.Tezos, - ) - - fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false - } -} - -fun Blockchain.isPayIdSupported(): Boolean { - return PayIdManager.payIdSupported.contains(this) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index f9be91dcce..4e39b73bbe 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,6 +1,9 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics @@ -134,22 +137,13 @@ class TapWalletManager( fun updateConfigManager(data: ScanResponse) { val configManager = store.state.globalState.configManager - val blockchain = data.cardTypesResolver.getBlockchain() + if (data.cardTypesResolver.isStart2Coin()) { - configManager?.turnOff(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED) - } else if (blockchain == Blockchain.Bitcoin || - data.walletData?.blockchain == Blockchain.Bitcoin.id - ) { - configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) - configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED) } else { - configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED) } } } -fun Wallet.getFirstToken(): Token? { - return getTokens().toList().getOrNull(0) -} \ No newline at end of file +fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index b1c97b594b..249ac0d0f4 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -26,6 +26,11 @@ interface UserWalletsListManager { * */ val hasUserWallets: Boolean + /** + * Count of saved user wallets + */ + val walletsCount: Int + /** * Set [UserWallet] with provided [UserWalletId] as selected * diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 4ea180e270..568d16fc28 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -53,6 +53,9 @@ internal class BiometricUserWalletsListManager( override val hasUserWallets: Boolean get() = keysRepository.hasSavedEncryptionKeys() + override val walletsCount: Int + get() = state.value.userWallets.size + override suspend fun unlock(): CompletionResult { return unlockWithBiometryInternal() .mapFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index c94958d46f..cfcd68bc57 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -7,13 +7,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletsListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.flow.updateAndGet +import kotlinx.coroutines.flow.* @OptIn(ExperimentalCoroutinesApi::class) internal class RuntimeUserWalletsListManager : UserWalletsListManager { @@ -36,6 +30,12 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { override val hasUserWallets: Boolean get() = state.value.userWallet != null + /** + * only 1 wallet stored in runtime implementation + */ + override val walletsCount: Int + get() = 1 + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { state.value.userWallet ?.takeIf { it.walletId == userWalletId } diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index acf3408acf..57598aa099 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -12,36 +12,47 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment -import com.google.accompanist.appcompattheme.AppCompatTheme +import androidx.fragment.app.activityViewModels import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.home.compose.StoriesScreen import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.features.home.redux.Stories import com.tangem.tap.features.tokens.legacy.redux.TokensAction import com.tangem.tap.store +import dagger.hilt.android.AndroidEntryPoint import org.rekotlin.StoreSubscriber +@AndroidEntryPoint class HomeFragment : Fragment(), StoreSubscriber { private var homeState: MutableState = mutableStateOf(store.state.homeState) + private val learn2earnViewModel by activityViewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) store.dispatch(HomeAction.OnCreate) store.dispatch(HomeAction.Init) + if (learn2earnViewModel.uiState.storyScreenState.isVisible) { + store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo)) + // re init homeState after inserting learn2earn story + homeState = mutableStateOf(store.state.homeState) + } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return ComposeView(inflater.context).apply { setContent { - BackHandler { - requireActivity().finish() - } - - AppCompatTheme { + TangemTheme { + BackHandler { + requireActivity().finish() + } ScreenContent() } } @@ -79,7 +90,8 @@ class HomeFragment : Fragment(), StoreSubscriber { @Composable private fun ScreenContent() { StoriesScreen( - homeState, + homeState = homeState, + onLearn2earnClick = learn2earnViewModel.uiState.storyScreenState.onClick, onScanButtonClick = { Analytics.send(IntroductionProcess.ButtonScanCard()) store.dispatch(HomeAction.ReadCard()) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index ef40d850f6..fa3e832436 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -5,29 +5,11 @@ package com.tangem.tap.features.home.compose import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.union -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.* import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -43,38 +25,40 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.tangem.tap.features.home.compose.content.FirstStoriesContent -import com.tangem.tap.features.home.compose.content.StoriesCurrencies -import com.tangem.tap.features.home.compose.content.StoriesRevolutionaryWallet -import com.tangem.tap.features.home.compose.content.StoriesUltraSecureBackup -import com.tangem.tap.features.home.compose.content.StoriesWalletForEveryone -import com.tangem.tap.features.home.compose.content.StoriesWeb3 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen +import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.StoriesProgressBar import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.features.home.redux.Stories import com.tangem.wallet.R import kotlin.math.max -private const val STEPS = 6 - @Suppress("LongMethod", "ComplexMethod") @Composable fun StoriesScreen( homeState: MutableState, + onLearn2earnClick: () -> Unit, onScanButtonClick: () -> Unit, onShopButtonClick: () -> Unit, onSearchTokensClick: () -> Unit, ) { - val currentStep = remember { mutableStateOf(1) } val systemUiController = rememberSystemUiController() + val state = homeState.value - val isDarkBackground = currentStep.value !in 3..5 + var currentStory by remember { mutableStateOf(state.firstStory) } + val currentStep = { state.stepOf(currentStory) } val goToPreviousScreen = { - currentStep.value = max(1, currentStep.value - 1) + currentStory = state.stories[max(0, currentStep() - 1)] } val goToNextScreen = { - currentStep.value = if (currentStep.value < STEPS) currentStep.value + 1 else 1 + currentStory = if (currentStep() < state.stories.lastIndex) { + state.stories[currentStep() + 1] + } else { + state.firstStory + } } val isPressed = remember { mutableStateOf(false) } @@ -82,10 +66,10 @@ fun StoriesScreen( val hideContent = remember { mutableStateOf(true) } - LaunchedEffect(key1 = isDarkBackground) { + LaunchedEffect(key1 = currentStory.isDarkBackground) { systemUiController.setSystemBarsColor( color = Color.Transparent, - darkIcons = !isDarkBackground, + darkIcons = !currentStory.isDarkBackground, ) } @@ -134,7 +118,7 @@ fun StoriesScreen( }, ) } - if (!isDarkBackground) { + if (!currentStory.isDarkBackground) { Image( modifier = Modifier.fillMaxSize(), painter = painterResource(id = R.drawable.ic_overlay), @@ -154,9 +138,9 @@ fun StoriesScreen( verticalArrangement = Arrangement.Center, ) { StoriesProgressBar( - steps = STEPS, - currentStep = currentStep.value, - stepDuration = currentStep.duration(), + steps = state.stories.lastIndex, + currentStep = currentStep(), + stepDuration = currentStory.duration, paused = isPaused, onStepFinish = goToNextScreen, ) @@ -169,15 +153,16 @@ fun StoriesScreen( .height(17.dp) .alpha(if (hideContent.value) 0f else 1f) .align(Alignment.Start), - colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black), + colorFilter = if (currentStory.isDarkBackground) null else ColorFilter.tint(Color.Black), ) - when (currentStep.value) { - 1 -> FirstStoriesContent(isPaused, currentStep.duration()) { hideContent.value = it } - 2 -> StoriesRevolutionaryWallet(currentStep.duration()) - 3 -> StoriesUltraSecureBackup(isPaused, currentStep.duration()) - 4 -> StoriesCurrencies(isPaused, currentStep.duration()) - 5 -> StoriesWeb3(isPaused, currentStep.duration()) - 6 -> StoriesWalletForEveryone(currentStep.duration()) + when (currentStory) { + Stories.OneInchPromo -> Learn2earnStoriesScreen(onLearn2earnClick) + Stories.TangemIntro -> FirstStoriesContent(isPaused, currentStory.duration) { hideContent.value = it } + Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet(currentStory.duration) + Stories.UltraSecureBackup -> StoriesUltraSecureBackup(isPaused, currentStory.duration) + Stories.Currencies -> StoriesCurrencies(isPaused, currentStory.duration) + Stories.Web3 -> StoriesWeb3(isPaused, currentStory.duration) + Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStory.duration) } } Column( @@ -186,7 +171,7 @@ fun StoriesScreen( .align(Alignment.BottomCenter) .fillMaxWidth(), ) { - if (currentStep.value == 4) { + if (currentStory == Stories.Currencies) { Button( onClick = onSearchTokensClick, modifier = Modifier @@ -210,29 +195,31 @@ fun StoriesScreen( ) } } - HomeButtons( - modifier = Modifier - .padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 37.dp) - .fillMaxWidth(), - isDarkBackground = isDarkBackground, - btnScanStateInProgress = homeState.value.btnScanStateInProgress, - onScanButtonClick = onScanButtonClick, - onShopButtonClick = onShopButtonClick, - ) + + if (currentStory != Stories.OneInchPromo) { + HomeButtons( + modifier = Modifier + .padding( + start = TangemTheme.dimens.size16, + end = TangemTheme.dimens.size16, + bottom = TangemTheme.dimens.size36, + ) + .fillMaxWidth(), + isDarkBackground = currentStory.isDarkBackground, + btnScanStateInProgress = homeState.value.btnScanStateInProgress, + onScanButtonClick = onScanButtonClick, + onShopButtonClick = onShopButtonClick, + ) + } } } } -@Suppress("MagicNumber") -private fun MutableState.duration(): Int = when (this.value) { - 1 -> 8000 - else -> 6000 -} - @Preview @Composable private fun StoriesScreenPreview() { StoriesScreen( + onLearn2earnClick = {}, onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {}, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt index f92adcf9d1..2774f0f683 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt @@ -4,12 +4,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -52,7 +47,7 @@ fun StoriesProgressBar( // .height() .padding(start = 9.dp, end = 9.dp, top = 16.dp), ) { - for (index in 1..steps) { + for (index in 0..steps) { Row( modifier = Modifier .height(2.dp) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index bb9be2edc3..86b139defb 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -11,6 +11,8 @@ sealed class HomeAction : Action { object OnCreate : HomeAction() object Init : HomeAction() + data class InsertStory(val position: Int, val story: Stories) : HomeAction() + data class ReadCard( val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction), ) : HomeAction() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index f9f2911c32..6843253f5a 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -12,6 +12,13 @@ private fun internalReduce(action: Action, state: AppState): HomeState { var state = state.homeState when (action) { + is HomeAction.InsertStory -> { + state = state.copy( + stories = state.stories.toMutableList().apply { + add(action.position, action.story) + }, + ) + } is HomeAction.ScanInProgress -> { state = state.copy(scanInProgress = action.scanInProgress) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 618e432dec..415deb938d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -8,8 +8,38 @@ import org.rekotlin.StateType data class HomeState( val scanInProgress: Boolean = false, val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED), + val stories: List = initDefaultStories(), ) : StateType { + val firstStory: Stories + get() = stories[0] + val btnScanStateInProgress: Boolean get() = btnScanState.progressState == ProgressState.Loading + + fun stepOf(story: Stories): Int = stories.indexOf(story) + + companion object { + fun initDefaultStories(): List = listOf( + Stories.TangemIntro, + Stories.RevolutionaryWallet, + Stories.UltraSecureBackup, + Stories.Currencies, + Stories.Web3, + Stories.WalletForEveryone, + ) + } +} + +sealed class Stories( + val isDarkBackground: Boolean, + val duration: Int, +) { + object OneInchPromo : Stories(true, duration = 8000) + object TangemIntro : Stories(true, duration = 8000) + object RevolutionaryWallet : Stories(true, duration = 6000) + object UltraSecureBackup : Stories(false, duration = 6000) + object Currencies : Stories(false, duration = 6000) + object Web3 : Stories(false, duration = 6000) + object WalletForEveryone : Stories(true, duration = 6000) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/IntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/IntentHandler.kt new file mode 100644 index 0000000000..ba008d4e5a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/IntentHandler.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.intentHandler + +import android.content.Intent + +/** +[REDACTED_AUTHOR] + */ +interface IntentHandler { + suspend fun handleIntent(intent: Intent?): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/IntentProcessor.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/IntentProcessor.kt new file mode 100644 index 0000000000..47cf6a31fd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/IntentProcessor.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.features.intentHandler + +import android.content.Intent +import java.util.concurrent.CopyOnWriteArrayList + +/** +[REDACTED_AUTHOR] + */ +// TODO: fixme: close it with the combined interfaces IntentHandler and IntentHandlerHolder +class IntentProcessor { + + private val intentHandlers = CopyOnWriteArrayList() + + fun addHandler(handler: IntentHandler) { + intentHandlers.add(handler) + } + + fun removeIntentHandler(handler: IntentHandler) { + intentHandlers.remove(handler) + } + + fun removeAll() { + intentHandlers.clear() + } + + suspend fun handleIntent(intent: Intent?) { + intentHandlers.forEach { + it.handleIntent(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt new file mode 100644 index 0000000000..18a51c222f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt @@ -0,0 +1,52 @@ +package com.tangem.tap.features.intentHandler.handlers + +import android.content.Intent +import android.nfc.NfcAdapter +import android.nfc.Tag +import android.os.Build +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.intentHandler.IntentHandler +import com.tangem.tap.features.welcome.redux.WelcomeAction +import com.tangem.tap.scope +import com.tangem.tap.store +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** +[REDACTED_AUTHOR] + */ +class BackgroundScanIntentHandler( + private val hasSavedUserWalletsProvider: () -> Boolean, +) : IntentHandler { + + private val nfcActions = arrayOf( + NfcAdapter.ACTION_NDEF_DISCOVERED, + NfcAdapter.ACTION_TECH_DISCOVERED, + NfcAdapter.ACTION_TAG_DISCOVERED, + ) + + override suspend fun handleIntent(intent: Intent?): Boolean { + if (intent == null || intent.action !in nfcActions) return false + + val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) + } + if (tag == null) return false + + intent.action = null + if (hasSavedUserWalletsProvider.invoke()) { + // TODO: Remove delay after [REDACTED_JIRA] + scope.launch { + delay(timeMillis = 200) + store.dispatch(WelcomeAction.ProceedWithCard) + } + } else { + store.dispatch(HomeAction.ReadCard()) + } + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt new file mode 100644 index 0000000000..39dda7cd24 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.intentHandler.handlers + +import android.content.Intent +import android.net.Uri +import com.tangem.core.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Token +import com.tangem.tap.features.intentHandler.IntentHandler +import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder +import com.tangem.tap.store + +/** +[REDACTED_AUTHOR] + */ +class BuyCurrencyIntentHandler : IntentHandler { + + override suspend fun handleIntent(intent: Intent?): Boolean { + val data = intent?.data ?: return false + val currency = store.state.walletState.selectedCurrency ?: return false + + val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL) + return if (data.host == successUri.host && data.authority == successUri.authority) { + val currencyType = AnalyticsParam.CurrencyType.Currency(currency) + Analytics.send(Token.Bought(currencyType)) + true + } else { + false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt new file mode 100644 index 0000000000..3fc6c318d6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.intentHandler.handlers + +import android.content.Intent +import com.tangem.tap.features.intentHandler.IntentHandler +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +class SellCurrencyIntentHandler : IntentHandler { + + override suspend fun handleIntent(intent: Intent?): Boolean { + return try { + val intentData = intent?.data ?: return false + val transactionID = intentData.getQueryParameter(TRANSACTION_ID_PARAM) ?: return false + val currency = intentData.getQueryParameter(CURRENCY_CODE_PARAM) ?: return false + val amount = intentData.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return false + val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false + + Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") + store.dispatch( + WalletAction.TradeCryptoAction.SendCrypto( + currencyId = currency, + amount = amount, + destinationAddress = destinationAddress, + transactionId = transactionID, + ), + ) + true + } catch (exception: Exception) { + Timber.d("Not MoonPay URL") + false + } + } + + private companion object { + private const val TRANSACTION_ID_PARAM = "transactionId" + private const val CURRENCY_CODE_PARAM = "baseCurrencyCode" + private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" + private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt new file mode 100644 index 0000000000..e61d0b74d7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.features.intentHandler.handlers + +import android.content.Intent +import com.tangem.tap.common.extensions.removePrefixOrNull +import com.tangem.tap.domain.walletconnect.WalletConnectManager +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.intentHandler.IntentHandler +import com.tangem.tap.store + +/** +[REDACTED_AUTHOR] + */ +class WalletConnectLinkIntentHandler : IntentHandler { + + override suspend fun handleIntent(intent: Intent?): Boolean { + val intentData = intent?.data ?: return false + val scheme = intent.scheme ?: return false + + val wcUri = when (scheme) { + WalletConnectManager.WC_SCHEME -> intentData.toString() + TANGEM_SCHEME -> intentData.toString().removePrefixOrNull(TANGEM_WC_PREFIX) + else -> null + } + + return if (wcUri == null) { + false + } else { + store.dispatch(WalletConnectAction.HandleDeepLink(wcUri)) + true + } + } + + private companion object { + private const val TANGEM_SCHEME = "tangem" + private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 20c206b37d..5f92c3c10b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered -import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError @@ -34,15 +33,14 @@ data class PrepareSendScreen( val tokenRate: BigDecimal? = null, ) : SendScreenAction -// Address or PayId -sealed class AddressPayIdActionUi : SendScreenActionUi { - data class HandleUserInput(val data: String) : AddressPayIdActionUi() - data class PasteAddressPayId(val data: String, val sourceType: AddressEntered.SourceType) : AddressPayIdActionUi() - data class CheckClipboard(val data: String?) : AddressPayIdActionUi() - data class CheckAddressPayId(val sourceType: AddressEntered.SourceType?) : AddressPayIdActionUi() - data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi() - data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi() - data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi() +// Address +sealed class AddressActionUi : SendScreenActionUi { + data class HandleUserInput(val data: String) : AddressActionUi() + data class PasteAddress(val data: String, val sourceType: AddressEntered.SourceType) : AddressActionUi() + data class CheckClipboard(val data: String?) : AddressActionUi() + data class CheckAddress(val sourceType: AddressEntered.SourceType?) : AddressActionUi() + data class SetTruncateHandler(val handler: (String) -> String) : AddressActionUi() + data class TruncateOrRestore(val truncate: Boolean) : AddressActionUi() } sealed class TransactionExtrasAction : SendScreenActionUi { @@ -76,27 +74,15 @@ sealed class TransactionExtrasAction : SendScreenActionUi { } } -sealed class AddressPayIdVerifyAction : SendScreenAction { +sealed class AddressVerifyAction : SendScreenAction { enum class Error { - PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN, - PAY_ID_NOT_REGISTERED, - PAY_ID_REQUEST_FAILED, ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN, ADDRESS_SAME_AS_WALLET, } - data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction() + data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressVerifyAction() - sealed class PayIdVerification : AddressPayIdVerifyAction() { - data class SetPayIdError(val error: Error?) : PayIdVerification() - data class SetPayIdWalletAddress( - val payId: String, - val payIdWalletAddress: String, - val isUserInput: Boolean, - ) : PayIdVerification() - } - - sealed class AddressVerification : AddressPayIdVerifyAction() { + sealed class AddressVerification : AddressVerifyAction() { data class SetAddressError(val error: Error?) : AddressVerification() data class SetWalletAddress(val address: String, val isUserInput: Boolean) : AddressVerification() } @@ -155,8 +141,6 @@ sealed class SendAction : SendScreenAction { override val messageResource: Int = R.string.send_transaction_success } - data class SendError(override val error: TapError) : SendAction(), ErrorAction - sealed class Dialog : SendAction(), StateDialog { data class TezosWarningDialog( val reduceCallback: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt similarity index 50% rename from app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt rename to app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt index 406eacd087..576497222e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt @@ -2,49 +2,39 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.PayIdManager -import com.tangem.tap.domain.isPayIdSupported import com.tangem.tap.features.send.redux.* -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress -import com.tangem.tap.scope -import com.tangem.tap.store -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetAddressError +import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetWalletAddress +import com.tangem.tap.features.send.redux.AddressVerifyAction.Error import org.rekotlin.Action import org.rekotlin.DispatchFunction /** [REDACTED_AUTHOR] */ -internal class AddressPayIdMiddleware { +internal class AddressMiddleware { - fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) { + fun handle(action: AddressActionUi, appState: AppState?, dispatch: (Action) -> Unit) { when (action) { - is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch) - is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, action.sourceType, dispatch) - is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch) - is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(action.sourceType, appState, dispatch) + is AddressActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch) + is AddressActionUi.PasteAddress -> pasteAddress(action.data, action.sourceType, dispatch) + is AddressActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch) + is AddressActionUi.CheckAddress -> verifyAddress(action.sourceType, appState, dispatch) else -> return } } private fun handleUserInput(input: String, appState: AppState?, dispatch: DispatchFunction) { val sendState = appState?.sendState ?: return - if (input == sendState.addressPayIdState.viewFieldValue.value) return + if (input == sendState.addressState.viewFieldValue.value) return setAddressAndCheck(data = input, sourceType = null, isUserInput = true, dispatch = dispatch) } - private fun pasteAddressPayId(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) { + private fun pasteAddress(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) { setAddressAndCheck(data = data, sourceType = sourceType, isUserInput = false, dispatch = dispatch) } @@ -54,74 +44,27 @@ internal class AddressPayIdMiddleware { isUserInput: Boolean, dispatch: (Action) -> Unit, ) { - val potentialPayId = data.lowercase() - if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) { - dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput)) - } else { - dispatch(SetWalletAddress(data, isUserInput)) - } - dispatch(AddressPayIdActionUi.CheckAddressPayId(sourceType)) + dispatch(SetWalletAddress(data, isUserInput)) + dispatch(AddressActionUi.CheckAddress(sourceType)) } - private fun verifyAddressPayId( + private fun verifyAddress( sourceType: AddressEntered.SourceType?, appState: AppState?, dispatch: (Action) -> Unit, ) { val sendState = appState?.sendState ?: return val wallet = sendState.walletManager?.wallet ?: return - val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return - val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput + val address = sendState.addressState.normalFieldValue ?: return + val isUserInput = sendState.addressState.viewFieldValue.isFromUserInput - if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) { - verifyPayId(addressPayId, wallet, isUserInput, dispatch) - } else { - verifyAddress( - address = addressPayId, - wallet = wallet, - isUserInput = isUserInput, - dispatch = dispatch, - sourceType = sourceType, - ) - } - } - - private fun verifyPayId(payId: String, wallet: Wallet, isUserInput: Boolean, dispatch: DispatchFunction) { - val blockchain = wallet.blockchain - if (!blockchain.isPayIdSupported()) { - dispatch(SetPayIdError(Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN)) - return - } - - scope.launch { - val result = PayIdManager().verifyPayId(payId, blockchain) - withContext(Dispatchers.Main) { - when (result) { - is Result.Success -> { - val addressDetails = result.data.getAddressDetails() - if (addressDetails == null) { - dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED)) - return@withContext - } - - val address = addressDetails.address - val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address) - if (failReason == null) { - dispatch(SetPayIdWalletAddress(payId, address, isUserInput)) - dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, addressDetails.tag)) - dispatch(FeeAction.RequestFee) - } else { - dispatch(SetAddressError(failReason)) - dispatch(TransactionExtrasAction.Release) - } - } - is Result.Failure -> { - dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED)) - dispatch(TransactionExtrasAction.Release) - } - } - } - } + verifyAddress( + address = address, + wallet = wallet, + isUserInput = isUserInput, + dispatch = dispatch, + sourceType = sourceType, + ) } private fun verifyAddress( @@ -219,41 +162,33 @@ internal class AddressPayIdMiddleware { } private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) { - val addressPayId = input ?: return + val address = input ?: return val wallet = appState?.sendState?.walletManager?.wallet ?: return val internalDispatcher: (Action) -> Unit = { when (it) { - is SetWalletAddress, is SetPayIdWalletAddress -> { - dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(true)) + is SetWalletAddress -> { + dispatch(AddressVerifyAction.ChangePasteBtnEnableState(true)) } - is SetAddressError, is SetPayIdError -> { - dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(false)) + is SetAddressError -> { + dispatch(AddressVerifyAction.ChangePasteBtnEnableState(false)) } } } - if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) { - verifyPayId(addressPayId, wallet, false, internalDispatcher) - } else { - verifyAddress( - address = addressPayId, - wallet = wallet, - sourceType = null, - isUserInput = false, - dispatch = internalDispatcher, - ) - } - } - - private fun isPayIdEnabled(): Boolean { - return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false + verifyAddress( + address = address, + wallet = wallet, + sourceType = null, + isUserInput = false, + dispatch = internalDispatcher, + ) } } fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map { - return this.split(firstDelimiter) + return this + .split(firstDelimiter) .map { it.split(secondDelimiter) } - .map { it.first() to it.last().toString() } - .toMap() + .associate { it.first() to it.last() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index 8a0b096c4c..3cbd54e044 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -38,7 +38,7 @@ class RequestFeeMiddleware { } val typedAmount = sendState.amountState.amountToExtract ?: return - val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!! + val destinationAddress = sendState.addressState.destinationWalletAddress!! val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto) val txSender = if (scanResponse.isDemoCard()) { DemoTransactionSender(walletManager) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index d54adda3d5..5f8d039210 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -55,18 +55,17 @@ class SendMiddleware { { nextDispatch -> { action -> when (action) { - is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch) + is AddressActionUi -> AddressMiddleware().handle(action, appState(), dispatch) is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch) is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch) is SendActionUi.SendAmountToRecipient -> verifyAndSendTransaction(action, appState(), dispatch) - is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch) is SendAction.Warnings.Update -> updateWarnings(dispatch) is SendActionUi.CheckIfTransactionDataWasProvided -> { val transactionData = appState()?.sendState?.externalTransactionData if (transactionData != null) { store.dispatchOnMain( - AddressPayIdVerifyAction.AddressVerification.SetWalletAddress( + AddressVerifyAction.AddressVerification.SetWalletAddress( address = transactionData.destinationAddress, isUserInput = false, ), @@ -96,7 +95,7 @@ private fun verifyAndSendTransaction( val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return val card = appState.globalState.scanResponse?.card ?: return - val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return + val destinationAddress = sendState.addressState.destinationWalletAddress ?: return val typedAmount = sendState.amountState.amountToExtract ?: return val feeAmount = sendState.feeState.currentFee ?: return @@ -391,12 +390,6 @@ fun createValidateTransactionError( return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") } } -private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) { - val isSendingToPayIdEnabled = - appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false - dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled)) -} - private fun updateWarnings(dispatch: (Action) -> Unit) { val warningsManager = store.state.globalState.warningManager ?: return val blockchain = store.state.sendState.walletManager?.wallet?.blockchain ?: return diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt deleted file mode 100644 index ddacac0df8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.tap.features.send.redux.reducers - -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification -import com.tangem.tap.features.send.redux.SendScreenAction -import com.tangem.tap.features.send.redux.states.AddressPayIdState -import com.tangem.tap.features.send.redux.states.InputViewValue -import com.tangem.tap.features.send.redux.states.SendState - -/** -[REDACTED_AUTHOR] - */ -class AddressPayIdReducer : SendInternalReducer { - override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { - is AddressPayIdActionUi -> handleUiAction(action, sendState, sendState.addressPayIdState) - is AddressPayIdVerifyAction -> handleAction(action, sendState, sendState.addressPayIdState) - else -> sendState - } - - private fun handleUiAction( - action: AddressPayIdActionUi, - sendState: SendState, - state: AddressPayIdState, - ): SendState { - val result = when (action) { - is AddressPayIdActionUi.HandleUserInput -> state - is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler) - is AddressPayIdActionUi.TruncateOrRestore -> { - val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: "" - state.copy(viewFieldValue = state.viewFieldValue.copy(value = value)) - } - is AddressPayIdActionUi.PasteAddressPayId -> return sendState - is AddressPayIdActionUi.CheckClipboard -> return sendState - is AddressPayIdActionUi.CheckAddressPayId -> return sendState - is AddressPayIdActionUi.ChangePayIdState -> state.copy(sendingToPayIdEnabled = action.sendingToPayIdEnabled) - } - return updateLastState(sendState.copy(addressPayIdState = result), result) - } - - private fun handleAction( - action: AddressPayIdVerifyAction, - sendState: SendState, - state: AddressPayIdState, - ): SendState { - val result = when (action) { - is PayIdVerification.SetPayIdWalletAddress -> { - state.copy( - viewFieldValue = InputViewValue(action.payId, action.isUserInput), - normalFieldValue = action.payId, - truncatedFieldValue = state.truncate(action.payId), - destinationWalletAddress = action.payIdWalletAddress, - error = null, - ) - } - is AddressVerification.SetWalletAddress -> { - state.copy( - viewFieldValue = InputViewValue(action.address, action.isUserInput), - normalFieldValue = action.address, - truncatedFieldValue = state.truncate(action.address), - destinationWalletAddress = action.address, - error = null, - ) - } - is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled) - is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null) - is PayIdVerification.SetPayIdError -> state.copy(error = action.error, destinationWalletAddress = null) - } - return updateLastState(sendState.copy(addressPayIdState = result), result) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt new file mode 100644 index 0000000000..a5b44bff0f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt @@ -0,0 +1,52 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.tap.features.send.redux.AddressActionUi +import com.tangem.tap.features.send.redux.AddressVerifyAction +import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification +import com.tangem.tap.features.send.redux.SendScreenAction +import com.tangem.tap.features.send.redux.states.AddressState +import com.tangem.tap.features.send.redux.states.InputViewValue +import com.tangem.tap.features.send.redux.states.SendState + +/** +[REDACTED_AUTHOR] + */ +class AddressReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { + is AddressActionUi -> handleUiAction(action, sendState, sendState.addressState) + is AddressVerifyAction -> handleAction(action, sendState, sendState.addressState) + else -> sendState + } + + private fun handleUiAction(action: AddressActionUi, sendState: SendState, state: AddressState): SendState { + val result = when (action) { + is AddressActionUi.HandleUserInput -> state + is AddressActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler) + is AddressActionUi.TruncateOrRestore -> { + val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: "" + state.copy(viewFieldValue = state.viewFieldValue.copy(value = value)) + } + is AddressActionUi.PasteAddress -> return sendState + is AddressActionUi.CheckClipboard -> return sendState + is AddressActionUi.CheckAddress -> return sendState + } + return updateLastState(sendState.copy(addressState = result), result) + } + + private fun handleAction(action: AddressVerifyAction, sendState: SendState, state: AddressState): SendState { + val result = when (action) { + is AddressVerification.SetWalletAddress -> { + state.copy( + viewFieldValue = InputViewValue(action.address, action.isUserInput), + normalFieldValue = action.address, + truncatedFieldValue = state.truncate(action.address), + destinationWalletAddress = action.address, + error = null, + ) + } + is AddressVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled) + is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null) + } + return updateLastState(sendState.copy(addressState = result), result) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 4134e8c148..463b0dcaea 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -25,7 +25,7 @@ object SendScreenReducer { val reducer: SendInternalReducer = when (action) { is PrepareSendScreen -> PrepareSendScreenStatesReducer() - is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() + is AddressActionUi, is AddressVerifyAction -> AddressReducer() is TransactionExtrasAction -> TransactionExtrasReducer() is AmountActionUi, is AmountAction -> AmountReducer() is FeeActionUi, is FeeAction -> FeeReducer() @@ -71,7 +71,7 @@ private class SendReducer : SendInternalReducer { amountState = state.amountState.copy( inputIsEnabled = false, ), - addressPayIdState = state.addressPayIdState.copy( + addressState = state.addressState.copy( inputIsEnabled = false, ), ) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt similarity index 90% rename from app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt rename to app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt index 9f0851e06d..b6b65a595f 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt @@ -2,17 +2,16 @@ package com.tangem.tap.features.send.redux.states import androidx.core.text.isDigitsOnly import com.tangem.blockchain.blockchains.stellar.StellarMemo -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AddressVerifyAction import java.math.BigInteger -data class AddressPayIdState( +data class AddressState( val viewFieldValue: InputViewValue = InputViewValue(""), val normalFieldValue: String? = null, val truncatedFieldValue: String? = null, val destinationWalletAddress: String? = null, - val error: AddressPayIdVerifyAction.Error? = null, + val error: AddressVerifyAction.Error? = null, val truncateHandler: ((String) -> String)? = null, - val sendingToPayIdEnabled: Boolean = false, val pasteIsEnabled: Boolean = false, val inputIsEnabled: Boolean = true, ) : SendScreenState { @@ -22,8 +21,6 @@ data class AddressPayIdState( fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value fun isReady(): Boolean = error == null && destinationWalletAddress?.isNotEmpty() ?: false - - fun isPayIdState(): Boolean = destinationWalletAddress != null && destinationWalletAddress != normalFieldValue } data class TransactionExtrasState( @@ -98,11 +95,7 @@ data class BinanceMemoState( val viewFieldValue: InputViewValue = InputViewValue(""), val memo: BigInteger? = null, val error: TransactionExtraError? = null, -) { - companion object { - val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16) - } -} +) // tag must contains only digits data class XrpDestinationTagState( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 6a47e2d342..8400f07c87 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -34,7 +34,7 @@ data class SendState( val coinConverter: CurrencyConverter? = null, val tokenConverter: CurrencyConverter? = null, val lastChangedStates: LinkedHashSet = linkedSetOf(), - val addressPayIdState: AddressPayIdState = AddressPayIdState(), + val addressState: AddressState = AddressState(), val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), val amountState: AmountState = AmountState(), val feeState: FeeState = FeeState(), @@ -52,7 +52,7 @@ data class SendState( MainCurrencyType.CRYPTO -> amountState.amountToExtract?.decimals ?: 0 } - fun convertFiatToCoin(value: BigDecimal): BigDecimal { + private fun convertFiatToCoin(value: BigDecimal): BigDecimal { return if (!this.coinIsConvertible()) value else coinConverter!!.toCrypto(value) } @@ -60,14 +60,14 @@ data class SendState( return if (!this.tokenIsConvertible()) value else tokenConverter!!.toCrypto(value) } - fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { + private fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { if (!this.coinIsConvertible()) return value val converter = coinConverter!! return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value) } - fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { + private fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { if (!this.tokenIsConvertible()) return value val converter = tokenConverter!! @@ -107,13 +107,13 @@ data class SendState( } companion object { - fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady() + private fun addressIsReady(): Boolean = store.state.sendState.addressState.isReady() - fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady() + private fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady() - fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady() + fun isReadyToRequestFee(): Boolean = addressIsReady() && amountIsReady() - fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() && + fun isReadyToSend(): Boolean = addressIsReady() && amountIsReady() && store.state.sendState.feeState.isReady() } } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 64c4fc53a7..fec34d9996 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -33,7 +33,7 @@ import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.send.redux.* -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* +import com.tangem.tap.features.send.redux.AddressActionUi.* import com.tangem.tap.features.send.redux.AmountActionUi.* import com.tangem.tap.features.send.redux.FeeActionUi.* import com.tangem.tap.features.send.redux.states.FeeType @@ -80,7 +80,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { etAmountToSend = view.findViewById(R.id.etAmountToSend) initSendButtonStates() - setupAddressOrPayIdLayout() + setupAddressLayout() setupTransactionExtrasLayout() setupAmountLayout() setupFeeLayout() @@ -99,14 +99,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { sendBtn = IndeterminateProgressButtonWidget(btnSend, progress) } - private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) { - store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") }) + private fun setupAddressLayout() = with(binding.lSendAddress) { + store.dispatch(SetTruncateHandler { etAddress.truncateMiddleWith(it, "...") }) store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString())) - etAddressOrPayId.apply { + etAddress.apply { setOnSystemPasteButtonClickListener { store.dispatch( - PasteAddressPayId( + PasteAddress( data = requireContext().getFromClipboard()?.toString() ?: "", sourceType = Token.Send.AddressEntered.SourceType.PastePopup, ), @@ -119,9 +119,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } + .filter { store.state.sendState.addressState.viewFieldValue.value != it } .onEach { - store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + store.dispatch(AddressActionUi.HandleUserInput(it)) } .launchIn(mainScope) } @@ -129,12 +129,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { imvPaste.setOnClickListener { Analytics.send(Token.Send.ButtonPaste()) store.dispatch( - PasteAddressPayId( + PasteAddress( data = requireContext().getFromClipboard()?.toString() ?: "", sourceType = Token.Send.AddressEntered.SourceType.PasteButton, ), ) - store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) + store.dispatch(TruncateOrRestore(!etAddress.isFocused)) } imvQrCode.setOnClickListener { Analytics.send(Token.Send.ButtonQRCode()) @@ -145,7 +145,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } } - private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) { + private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) { // TODO: [REDACTED_TASK_KEY] etXlmMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -202,15 +202,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { // 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 - binding.lSendAddressPayid.imvQrCode.postDelayed( + binding.lSendAddress.imvQrCode.postDelayed( { store.dispatch( - PasteAddressPayId( + PasteAddress( data = scannedCode, sourceType = Token.Send.AddressEntered.SourceType.QRCode, ), ) - store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) + store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused)) }, 200, ) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 12f16a0274..7c12e82496 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -7,30 +7,15 @@ import android.view.View import android.view.ViewGroup import androidx.core.text.bold import com.tangem.common.extensions.remove -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.enableError -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.getString -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.update +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.getMessageString import com.tangem.tap.common.text.DecimalDigitsInputFilter import com.tangem.tap.domain.MultiMessageError import com.tangem.tap.domain.assembleErrors import com.tangem.tap.features.BaseStoreFragment -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error +import com.tangem.tap.features.send.redux.AddressVerifyAction.Error import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.states.AddressPayIdState -import com.tangem.tap.features.send.redux.states.AmountState -import com.tangem.tap.features.send.redux.states.FeeState -import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.ReceiptLayoutType -import com.tangem.tap.features.send.redux.states.ReceiptState -import com.tangem.tap.features.send.redux.states.SendState -import com.tangem.tap.features.send.redux.states.StateId -import com.tangem.tap.features.send.redux.states.TransactionExtraError -import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.send.ui.FeeUiHelper import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.send.ui.dialogs.KaspaWarningDialog @@ -61,7 +46,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber lastChangedStates.forEach { when (it) { StateId.SEND_SCREEN -> handleSendScreen(fg, state) - StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState) + StateId.ADDRESS_PAY_ID -> handleAddressState(fg, state.addressState) StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState) StateId.AMOUNT -> handleAmountState(fg, state.amountState) StateId.FEE -> handleFeeState(fg, state.feeState) @@ -72,7 +57,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber @Suppress("ComplexMethod") private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) = - with(fg.binding.lSendAddressPayid) { + with(fg.binding.lSendAddress) { fun showView(view: View, info: Any?) { view.show(info != null) } @@ -192,13 +177,10 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber ) } - private fun handleAddressPayIdState(fg: SendFragment, state: AddressPayIdState) = - with(fg.binding.lSendAddressPayid) { + private fun handleAddressState(fg: SendFragment, state: AddressState) { + with(fg.binding.lSendAddress) { fun parseError(context: Context, error: Error?): String? { val resId = when (error) { - Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain - Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered - Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet else -> null @@ -208,8 +190,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber imvPaste.isEnabled = state.pasteIsEnabled - val et = etAddressOrPayId - val til = tilAddressOrPayId + val et = etAddress + val til = tilAddress val parsedError = parseError(til.context, state.error) til.isEnabled = state.inputIsEnabled @@ -218,19 +200,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber flPaste.show(state.inputIsEnabled) flQrCode.show(state.inputIsEnabled) - val hintResId = if (state.sendingToPayIdEnabled) { - R.string.send_destination_hint_address_payid - } else { - R.string.send_destination_hint_address - } - til.hint = til.getString(hintResId) + til.hint = til.getString(R.string.send_destination_hint_address) til.error = parsedError til.isErrorEnabled = parsedError != null til.helperText = state.destinationWalletAddress - til.isHelperTextEnabled = state.isPayIdState() && parsedError == null + til.isHelperTextEnabled = parsedError == null if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value) } + } private fun handleAmountState(fg: SendFragment, state: AmountState) = with(fg.binding.lSendAmount) { if (state.error != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 47d07b91b0..6540470882 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -8,7 +8,9 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle @@ -22,12 +24,16 @@ import coil.size.Scale import com.badoo.mvicore.modelWatcher import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel +import com.tangem.feature.learn2earn.presentation.ui.Learn2earnMainPageScreen import com.tangem.feature.swap.api.SwapFeatureToggleManager import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.events.Portfolio +import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.show import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.global.GlobalAction @@ -73,6 +79,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber() private val viewModel by viewModels() private val totalBalanceWatcher = modelWatcher { @@ -102,6 +109,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber 1 && currency.blockchain != Blockchain.BitcoinCash + return listOfAddresses.size > 1 } internal fun WalletDataModel.assembleWarnings( diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt index a0484fc798..e7ce3e5957 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -15,7 +15,7 @@ internal sealed interface WelcomeAction : Action { data class Error(val error: TangemError) : WelcomeAction } - data class HandleIntentIfNeeded(val intent: Intent?) : WelcomeAction + data class SetInitialIntent(val intent: Intent?) : WelcomeAction object CloseError : WelcomeAction diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 9955bf6d6d..005bcca0a4 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.welcome.redux -import android.content.Intent import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnResult @@ -19,6 +18,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable +import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.signin.redux.SignInAction import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -39,24 +39,10 @@ internal class WelcomeMiddleware { private fun handleAction(action: WelcomeAction, state: WelcomeState) { when (action) { - is WelcomeAction.ProceedWithBiometrics -> { - proceedWithBiometrics(state) - } - is WelcomeAction.ProceedWithCard -> { - proceedWithCard(state) - } - is WelcomeAction.HandleIntentIfNeeded -> { - handleInitialIntent(action.intent) - } - is WelcomeAction.ClearUserWallets -> { - disableUserWalletsSaving() - } - is WelcomeAction.ProceedWithBiometrics.Error, - is WelcomeAction.ProceedWithCard.Error, - is WelcomeAction.ProceedWithBiometrics.Success, - is WelcomeAction.ProceedWithCard.Success, - is WelcomeAction.CloseError, - -> Unit + is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(state) + is WelcomeAction.ProceedWithCard -> proceedWithCard(state) + is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving() + else -> Unit } } @@ -85,7 +71,9 @@ internal class WelcomeMiddleware { store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(userWallet = selectedUserWallet) - intentHandler.handleWalletConnectLink(state.intent) + state.intent?.let { + WalletConnectLinkIntentHandler().handleIntent(it) + } } } @@ -104,20 +92,13 @@ internal class WelcomeMiddleware { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet = userWallet) - intentHandler.handleWalletConnectLink(state.intent) + state.intent?.let { + WalletConnectLinkIntentHandler().handleIntent(it) + } } } } - private fun handleInitialIntent(intent: Intent?) { - val isBackgroundScanNotHandled = !intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) - val hasNotIncompletedBackup = !backupService.hasIncompletedBackup - - if (isBackgroundScanNotHandled && hasNotIncompletedBackup) { - store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) - } - } - private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt index 5f14d1744d..11bfb0ea4a 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -14,7 +14,7 @@ internal object WelcomeReducer { private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState { return when (action) { - is WelcomeAction.HandleIntentIfNeeded -> state.copy(intent = action.intent) + is WelcomeAction.SetInitialIntent -> state.copy(intent = action.intent) is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true) is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true) is WelcomeAction.ProceedWithBiometrics.Error -> state.copy( diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt deleted file mode 100644 index d9d00a663b..0000000000 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.tap.network.payid - -import com.squareup.moshi.JsonClass -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Path - -/** -[REDACTED_AUTHOR] - */ -interface PayIdVerifyApi { - @GET("{user}") - suspend fun verifyAddress( - @Path("user") user: String, - @Header("Accept") acceptNetworkHeader: String, - @Header("PayID-Version") payIdVersion: String = "1.0", - ): VerifyPayIdResponse -} - -@JsonClass(generateAdapter = true) -data class VerifyPayIdResponse( - val addresses: List = mutableListOf(), - val payId: String? = null, -) { - fun getAddressDetails(): PayIdAddressDetails? = if (addresses.isNotEmpty()) addresses[0].addressDetails else null -} - -@JsonClass(generateAdapter = true) -data class PayIdAddress( - var paymentNetwork: String, - var environment: String, - var addressDetailsType: String, - var addressDetails: PayIdAddressDetails, -) - -@JsonClass(generateAdapter = true) -data class PayIdAddressDetails( - var address: String, - var tag: String? = null, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt deleted file mode 100644 index 05d16f8724..0000000000 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.tap.network.payid - -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.datasource.api.common.createRetrofitInstance - -/** -[REDACTED_AUTHOR] - */ -class PayIdVerifyService( - private val baseUrl: String, -) { - - private val api = createRetrofitInstance( - baseUrl = baseUrl, - logEnabled = false, - ).create(PayIdVerifyApi::class.java) - - suspend fun verifyAddress(user: String, network: String): Result { - return performRequest { api.verifyAddress(user, createNetworkHeader(network)) } - } - - private fun createNetworkHeader(network: String): String = "application/$network-mainnet+json" -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 1189c0895d..371f1e21bf 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -1,6 +1,8 @@ package com.tangem.tap.proxy.di +import androidx.compose.ui.text.intl.Locale import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager @@ -48,4 +50,20 @@ class ProxyModule { appStateHolder = appStateHolder, ) } + + // regions FeatureConsumers + @Provides + @Singleton + fun provideLear2earnDependencies(appStateHolder: AppStateHolder): Learn2earnDependencyProvider { + return object : Learn2earnDependencyProvider { + override fun getUserCountryCodeProvider(): () -> String = { + appStateHolder.mainStore?.state?.globalState?.userCountryCode ?: Locale.current.language + } + + override fun getWebViewAuthCredentialsProvider(): () -> String? = { + appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization + } + } + } + // endregion FeatureConsumers } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_wallet_trade.xml b/app/src/main/res/layout/dialog_wallet_trade.xml index b2e6a5e45b..dea867e05a 100644 --- a/app/src/main/res/layout/dialog_wallet_trade.xml +++ b/app/src/main/res/layout/dialog_wallet_trade.xml @@ -23,7 +23,7 @@ android:focusable="true" android:gravity="center_vertical" android:padding="16dp" - android:text="@string/wallet_button_buy" + android:text="@string/common_buy" android:textColor="@color/darkGray3" android:textSize="14sp" android:textStyle="bold" @@ -38,7 +38,7 @@ android:focusable="true" android:gravity="center_vertical" android:padding="16dp" - android:text="@string/wallet_button_sell" + android:text="@string/common_sell" android:textColor="@color/darkGray3" android:textSize="14sp" android:textStyle="bold" diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index fe0eebf2a3..4035f923e2 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -22,7 +22,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" - app:title="@string/send_title" /> + app:title="@string/common_send" /> @@ -39,8 +39,8 @@ android:orientation="vertical"> @@ -115,7 +115,7 @@ style="@style/TapPrimaryIconButton" android:layout_width="match_parent" android:fontFamily="@font/saira_semi_condensed_regular" - android:text="@string/send_title" + android:text="@string/common_send" app:icon="@drawable/ic_arrow_right" /> + + - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address.xml similarity index 84% rename from app/src/main/res/layout/layout_send_address_payid.xml rename to app/src/main/res/layout/layout_send_address.xml index 1bf69a649f..c6dd2873c8 100644 --- a/app/src/main/res/layout/layout_send_address_payid.xml +++ b/app/src/main/res/layout/layout_send_address.xml @@ -18,10 +18,9 @@ app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toTopOf="@+id/tilAddress"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@+id/tilAddress"> @@ -51,7 +51,7 @@ style="@style/TapPrimaryIconButton" android:layout_width="0dp" android:layout_weight="1" - android:text="@string/wallet_button_send" + android:text="@string/common_send" app:icon="@drawable/ic_send" /> diff --git a/app/src/tangemAccess/java/com/tangem/Test2.java b/app/src/tangemAccess/java/com/tangem/Test2.java deleted file mode 100644 index c4c336af47..0000000000 --- a/app/src/tangemAccess/java/com/tangem/Test2.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tangem; - -public class Test2 { -} diff --git a/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt deleted file mode 100644 index f934c827fc..0000000000 --- a/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt +++ /dev/null @@ -1,305 +0,0 @@ -package com.tangem.ui - -import android.app.Activity -import android.content.SharedPreferences -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Build -import android.os.Bundle -import android.preference.PreferenceManager -import android.text.Editable -import android.text.Html -import android.text.TextWatcher -import android.util.Log -import android.view.View -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.core.os.bundleOf -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.pin.PinRequestFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.UtilHelper -import com.tangem.wallet.CoinEngine -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.tangemAccess.fragment_confirm_transaction.* -import java.io.IOException -import java.util.* - -class ConfirmTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback { - - override val layoutId = R.layout.fragment_confirm_transaction - - private lateinit var sp: SharedPreferences - private lateinit var ctx: TangemContext - private lateinit var amount: CoinEngine.Amount - - private var isIncludeFee: Boolean = true - private var requestPIN2Count = 0 - private var nodeCheck = true - private var dtVerified: Date? = null - - private var blockchainCallbacks: CoinEngine.BlockchainRequestsCallbacks? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - sp = PreferenceManager.getDefaultSharedPreferences(context) - ctx = TangemContext.loadFromBundle(requireContext(), arguments) - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - navigateUp() - } - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val engine = CoinEngineFactory.create(ctx) - - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine!!.balanceHTML) - tvBalance.text = html - - isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true - - if (isIncludeFee) - tvIncFee.setText(R.string.confirm_transaction_including_fee) - else - tvIncFee.setText(R.string.confirm_transaction_not_including_fee) - - amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT) ?: "0", - arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY) ?: "") - - if (engine.allowSelectFeeInclusion()) - tvIncFee.visibility = View.VISIBLE - else - tvIncFee.visibility = View.INVISIBLE - - if (ctx.card.blockchainID == Blockchain.Token.id) { - // for Blockchain.Token limit decimals - etAmount.setText(amount.toValueString(ctx.card.tokensDecimal)) - } else { - // for others - etAmount.setText(amount.toValueString()) - } - - tvCurrency.text = engine.balanceCurrency - tvCurrency2.text = engine.feeCurrency - tvCardID.text = ctx.card.cidDescription - etWallet.setText(arguments?.getString(Constant.EXTRA_TARGET_ADDRESS)) - - btnSend.visibility = View.INVISIBLE - - if (!engine.allowSelectFeeLevel()) { - rgFee.visibility = View.INVISIBLE - } - - etFee.isEnabled = sp.getBoolean(getString(R.string.pref_manual_editing_fee), false) - - // set listeners - rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) } - etFee.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - - } - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - try { - val eqFee = engine.evaluateFeeEquivalent(etFee!!.text.toString()) - tvFeeEquivalent.text = eqFee - - if (!ctx.coinData!!.amountEquivalentDescriptionAvailable) { - tvFeeEquivalent.error = getString(R.string.confirm_transaction_error_service_unavailable) - tvCurrency2.visibility = View.GONE - tvFeeEquivalent.visibility = View.GONE - } else - tvFeeEquivalent.error = null - - if (sp.getBoolean(getString(R.string.pref_manual_editing_fee), false)) - (activity as MainActivity).toastHelper - .showSingleToast(context, getString(R.string.confirm_transaction_warning_risk_delaying)) - - } catch (e: Exception) { - e.printStackTrace() - tvFeeEquivalent.text = "" - } - } - - override fun afterTextChanged(s: Editable) { - - } - }) - btnSend.setOnClickListener { - if (UtilHelper.isOnline(requireContext())) { - val calendar = Calendar.getInstance() - calendar.add(Calendar.MINUTE, -1) - - if (dtVerified == null || dtVerified!!.before(calendar.time)) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_data_is_outdated)) - return@setOnClickListener - } - - val engineCoin = CoinEngineFactory.create(ctx) - - if (engineCoin!!.isNeedCheckNode && !nodeCheck) { - Toast.makeText(context, getString(R.string.confirm_transaction_error_cannot_reach_node), Toast.LENGTH_LONG).show() - return@setOnClickListener - } - - val txFee = engineCoin.convertToAmount(etFee.text.toString(), tvCurrency2.text.toString()) - val txAmount = engineCoin.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString()) - - if (!engineCoin.hasBalanceInfo()) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_cannot_check_balance)) - return@setOnClickListener - - } else if (!engineCoin.isBalanceNotZero) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.general_wallet_empty)) - return@setOnClickListener - - } else if (!engineCoin.isExtractPossible) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_incoming_transaction_unconfirmed)) - return@setOnClickListener - } - - if (!engineCoin.checkNewTransactionAmountAndFee(txAmount, txFee, isIncludeFee)) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.prepare_transaction_error_not_enough_funds)) - return@setOnClickListener - } - - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(data) - data.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_, R.id.action_confirmTransactionFragment_to_pinRequestFragment, data) - } else - Toast.makeText(context, getString(R.string.general_error_no_connection), Toast.LENGTH_SHORT).show() - } - - progressBar.visibility = View.VISIBLE - - if (!navigatedBack) requestFee() - } - - private fun requestFee() { - val coinEngine = CoinEngineFactory.create(ctx) - coinEngine!!.requestFee( - object : CoinEngine.BlockchainRequestsCallbacks { - override fun onComplete(success: Boolean) { - if (success) { - progressBar?.visibility = View.INVISIBLE - dtVerified = Date() - doSetFee(rgFee?.checkedRadioButtonId ?: R.id.rbNormalFee) - } else { - finishWithError(Activity.RESULT_CANCELED, ctx.error) - } - } - - override fun onProgress() { - } - - override fun allowAdvance(): Boolean { - return UtilHelper.isOnline(requireContext()) - } - }, - etWallet.text.toString(), - amount) - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - Log.d("LIFECYCLE", "NavigationResult assessed ${this::class.java.simpleName}") - if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) { - if (data != null) { - if (data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - } - if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) { - requestPIN2Count++ - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_, - R.id.action_confirmTransactionFragment_to_pinRequestFragment, - bundle) - return - } - navigateBackWithResult(resultCode, data) - } else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) { - if (resultCode == Activity.RESULT_OK) { - val bundle = Bundle() - ctx.saveToBundle(bundle) - bundle.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString()) - bundle.putString(Constant.EXTRA_AMOUNT, etAmount.text.toString()) - bundle.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString()) - bundle.putString(Constant.EXTRA_FEE, etFee.text.toString()) - bundle.putString(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString()) - bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee) - navigateForResult(Constant.REQUEST_CODE_SIGN_TRANSACTION, - R.id.action_confirmTransactionFragment_to_signTransactionFragment, - bundle) - } else - Toast.makeText(context, R.string.confirm_transaction_error_pin_2_is_required, Toast.LENGTH_LONG).show() - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - - private fun doSetFee(checkedRadioButtonId: Int) { - var txtFee = "" - when (checkedRadioButtonId) { - R.id.rbMinimalFee -> - if (ctx.coinData.minFee != null) { - txtFee = ctx.coinData.minFee!!.toValueString() - btnSend?.visibility = View.VISIBLE - } else - btnSend?.visibility = View.INVISIBLE - - R.id.rbNormalFee -> - if (ctx.coinData.normalFee != null) { - txtFee = ctx.coinData.normalFee!!.toValueString() - btnSend?.visibility = View.VISIBLE - } else - btnSend?.visibility = View.INVISIBLE - - R.id.rbMaximumFee -> - if (ctx.coinData.maxFee != null) { - txtFee = ctx.coinData.maxFee!!.toValueString() - btnSend?.visibility = View.VISIBLE - } else - btnSend?.visibility = View.INVISIBLE - } - etFee?.setText(txtFee.replace(',', '.')) - } - - private fun finishWithError(errorCode: Int, message: String) { - navigateBackWithResult( - errorCode, - bundleOf(Constant.EXTRA_MESSAGE to message), - R.id.loadedWalletFragment) - } -} \ No newline at end of file diff --git a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt deleted file mode 100644 index 387f992bb6..0000000000 --- a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt +++ /dev/null @@ -1,182 +0,0 @@ -package com.tangem.ui - -import android.app.Activity -import android.content.Context -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Build -import android.os.Bundle -import android.text.Html -import android.view.View -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.Toast -import com.tangem.Constant -import com.tangem.data.isPayIdSupported -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.qr.CameraPermissionManager -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.UtilHelper -import com.tangem.util.extensions.isStart2CoinCard -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.tangemAccess.fragment_prepare_transaction.* -import java.io.IOException - -class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback { - companion object { - val TAG: String = PrepareTransactionFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_prepare_transaction - - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - tvCardID.text = ctx.card?.cidDescription - val engine = CoinEngineFactory.create(ctx) - - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine!!.balanceHTML) - tvBalance.text = html - - if (ctx.blockchain.isPayIdSupported() && !ctx.card.isStart2CoinCard()) { - etWallet.hint = getString(R.string.prepare_transaction_hint_address_or_pay_id) - } - - if (!engine.allowSelectFeeInclusion()) { - rgIncFee.visibility = View.INVISIBLE - } else { - rgIncFee.visibility = View.VISIBLE - } - - if (ctx.card!!.remainingSignatures < 2) { - etAmount.isEnabled = false - } - - if (ctx.card.remainingSignatures == 1) { - androidx.appcompat.app.AlertDialog.Builder(requireContext()) - .setTitle(R.string.prepare_transaction_warning_last_signature) - .setMessage(R.string.prepare_transaction_warning_send_full_amount) - .setPositiveButton(R.string.general_ok) { _, _ -> } - .create() - .show() - } - - tvCurrency.text = engine.balance.currency - etAmount.setText(engine.balance.toValueString()) - - // limit number of symbols after comma - etAmount.filters = engine.amountInputFilters - - // set listeners - etAmount.setOnEditorActionListener { lv, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(lv.windowToken, 0) - lv.clearFocus() - true - } else { - false - } - } - - btnVerify.setOnClickListener { - if (!UtilHelper.isOnline(requireContext())) { - Toast.makeText(context, R.string.general_error_no_connection, Toast.LENGTH_LONG).show() - return@setOnClickListener - } - - val engine1 = CoinEngineFactory.create(ctx) - val strAmount: String = etAmount.text.toString().replace(",", ".") - val amount = engine1!!.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString()) - - try { - if (!engine.checkNewTransactionAmount(amount)) - etAmount.error = getString(R.string.prepare_transaction_error_not_enough_funds) - else - etAmount.error = null - } catch (e: Exception) { - etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format) - } - - // check wallet address - if (!engine1.validateAddress(etWallet.text.toString())) { - etWallet.error = getString(R.string.prepare_transaction_error_incorrect_destination) - return@setOnClickListener - } else - etWallet.error = null - - if (etWallet.text.toString() == ctx.coinData!!.wallet) { - etWallet.error = getString(R.string.prepare_transaction_error_same_address) - return@setOnClickListener - } - - if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) { - return@setOnClickListener - } - - val data = Bundle() - ctx.saveToBundle(data) - data.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString()) - data.putBoolean(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn)) - data.putString(Constant.EXTRA_AMOUNT, strAmount) - data.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString()) - navigateForResult( - Constant.REQUEST_CODE_SEND_TRANSACTION__, - R.id.action_prepareTransactionFragment_to_confirmTransactionFragment, - data) - } - - ivCamera.setOnClickListener { - if (cameraPermissionManager.isPermissionGranted()) { - navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment) - } else { - cameraPermissionManager.requirePermission() - } - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) { - navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment) - } - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) { - val code = data.getString("QRCode") - val schemeSplit = code!!.split(":") - when (schemeSplit.size) { - 2 -> { - if (schemeSplit[0] == ctx.blockchain.uriScheme) { - etWallet?.setText(schemeSplit[1]) - } else { - etWallet?.setText(code) - } - } - else -> { - etWallet?.setText(code) - } - } - } else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) { - navigateBackWithResult(resultCode, data) - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } -} \ No newline at end of file diff --git a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt deleted file mode 100644 index fdcdc64a72..0000000000 --- a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt +++ /dev/null @@ -1,312 +0,0 @@ -package com.tangem.ui - -import android.app.Activity -import android.content.res.ColorStateList -import android.graphics.Color -import android.media.MediaPlayer -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.SignTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.Analytics -import com.tangem.util.AnalyticsEvent -import com.tangem.util.LOG -import com.tangem.wallet.CoinEngine -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.layout_progress_horizontal.* -import kotlinx.android.synthetic.main.layout_touch_card.* -import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.* - - -class SignTransactionFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback, CardProtocol.Notifications { - - companion object { - val TAG: String = SignTransactionFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_sign_transaction - - private lateinit var ctx: TangemContext - private lateinit var mpFinishSignSound: MediaPlayer - - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - - private var signTransactionTask: SignTask? = null - - private lateinit var amount: CoinEngine.Amount - private lateinit var fee: CoinEngine.Amount - private var isIncludeFee = true - private var outAddressStr: String? = null - private var lastReadSuccess = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - navigateBackWithResult(Activity.RESULT_CANCELED) - } - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound) - - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT), arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY)) - fee = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_FEE), arguments?.getString(Constant.EXTRA_FEE_CURRENCY)) - isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true - outAddressStr = arguments?.getString(Constant.EXTRA_TARGET_ADDRESS) - - tvCardID.text = ctx.card!!.cidDescription - progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar.visibility = View.INVISIBLE - - FirebaseAnalytics.getInstance(requireActivity()) - .logEvent(AnalyticsEvent.READY_TO_SIGN.event, Analytics.setCardData(ctx)) - } - - override fun onPause() { - signTransactionTask?.cancel(true) - super.onPause() - } - - override fun onStop() { - signTransactionTask?.cancel(true) - super.onStop() - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) { - navigateBackWithResult(resultCode, data) - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - - if (sUID == ctx.card.uid) { - if (lastReadSuccess) - isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000 - else - isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000 - - val coinEngine = CoinEngineFactory.create(ctx) - coinEngine?.setOnNeedSendTransaction { tx -> - if (tx != null) { - val data = Bundle() - ctx.saveToBundle(data) - data.putByteArray(Constant.EXTRA_TX, tx) - navigateForResult( - Constant.REQUEST_CODE_SEND_TRANSACTION_, - R.id.action_signTransactionFragment_to_sendTransactionFragment, - data) - } - } - val transactionToSign = coinEngine?.constructTransaction(amount, fee, isIncludeFee, outAddressStr) - - signTransactionTask = SignTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this, transactionToSign) - signTransactionTask?.start() - } else - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - - } catch (e: CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } catch(e: IllegalArgumentException) { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, e.message) - navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment) - } catch (e: Exception) { - e.printStackTrace() - } - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - signTransactionTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - - FirebaseAnalytics.getInstance(requireActivity()) - .logEvent(AnalyticsEvent.SIGNED.event, Analytics.setCardData(ctx)) - - rlProgressBar?.post { rlProgressBar?.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - } - - mpFinishSignSound.start() - } else { - lastReadSuccess = false - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data) - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } else { - if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } - progressBar?.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) { - NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString() - NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG) - } - } else { - (activity as? MainActivity)?.toastHelper?.showSingleToast( - context, getString(R.string.general_notification_scan_again) - ) - } - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadCancel() { - signTransactionTask = null - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - -// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() - - override fun onReadBeforeRequest(timeout: Int) { - LOG.i(TAG, "onReadBeforeRequest timeout $timeout") - activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) } - -// if (!waitSecurityDelayDialogNew.isAdded) -// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) - - -// val readBeforeRequest = ReadBeforeRequest() -// readBeforeRequest.timeout = timeout -// EventBus.getDefault().post(readBeforeRequest) - } - - override fun onReadAfterRequest() { - LOG.i(TAG, "onReadAfterRequest") - activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) } - -// val readAfterRequest = ReadAfterRequest() -// EventBus.getDefault().post(readAfterRequest) - } - - override fun onReadWait(msec: Int) { - LOG.i(TAG, "onReadWait msec $msec") - activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) } - -// val readWait = ReadWait() -// readWait.msec = msec -// EventBus.getDefault().post(readWait) - } - -} \ No newline at end of file diff --git a/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml b/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml deleted file mode 100644 index 6bb5f43dda..0000000000 --- a/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -