Updated on 2026-08-14
This commit is contained in:
commit
71697335b9
901 changed files with 12751 additions and 23160 deletions
|
|
@ -76,11 +76,10 @@ dependencies {
|
|||
implementation(projects.data.txhistory)
|
||||
implementation(projects.data.wallets)
|
||||
implementation(projects.data.analytics)
|
||||
implementation(projects.data.transaction)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
implementation(projects.features.learn2earn.api)
|
||||
implementation(projects.features.learn2earn.impl)
|
||||
implementation(projects.features.referral.presentation)
|
||||
implementation(projects.features.referral.domain)
|
||||
implementation(projects.features.referral.data)
|
||||
|
|
@ -99,6 +98,8 @@ dependencies {
|
|||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.manageTokens.impl)
|
||||
implementation(projects.features.send.impl)
|
||||
implementation(projects.features.qrScanning.api)
|
||||
implementation(projects.features.qrScanning.impl)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
@ -146,6 +147,7 @@ dependencies {
|
|||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Other libraries */
|
||||
|
|
@ -158,7 +160,6 @@ dependencies {
|
|||
implementation(deps.timber)
|
||||
implementation(deps.reKotlin)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.otaliastudiosCameraView)
|
||||
implementation(deps.coil)
|
||||
implementation(deps.appsflyer)
|
||||
implementation(deps.amplitude)
|
||||
|
|
@ -186,7 +187,9 @@ dependencies {
|
|||
}
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
androidTestImplementation(deps.test.junit.android)
|
||||
androidTestImplementation(deps.test.espresso)
|
||||
|
|
@ -208,7 +211,7 @@ dependencies {
|
|||
/** Excluded dependencies */
|
||||
implementation("com.google.guava:guava:30.0-android") {
|
||||
// excludes version 9999.0-empty-to-avoid-conflict-with-guava
|
||||
exclude(group="com.google.guava", module = "listenablefuture")
|
||||
exclude(group = "com.google.guava", module = "listenablefuture")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -137,14 +137,6 @@
|
|||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
|
|
|||
|
|
@ -402,6 +402,28 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "vechain",
|
||||
"symbol" : "VET",
|
||||
"name" : "Vechain",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "vechain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vethor-token",
|
||||
"symbol": "VTHO",
|
||||
"name": "VeThor",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "vechain/test",
|
||||
"contractAddress": "0x0000000000000000000000000000456E65726779",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "tron",
|
||||
"symbol" : "TRX",
|
||||
|
|
@ -536,6 +558,26 @@
|
|||
"networkId": "decimal/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "xdce-crowd-sale",
|
||||
"name": "XDC",
|
||||
"symbol": "XDC",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "xdc-network/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "aptos",
|
||||
"name": "Aptos",
|
||||
"symbol": "APT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "aptos/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.lifecycle.flowWithLifecycle
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import arrow.core.getOrElse
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
|
|
@ -134,6 +135,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var sendRouter: SendRouter
|
||||
|
||||
@Inject
|
||||
lateinit var qrScanningRouter: QrScanningRouter
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>
|
||||
|
|
@ -206,6 +210,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
manageTokensRouter = manageTokensRouter,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
sendRouter = sendRouter,
|
||||
qrScanningRouter = qrScanningRouter,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -247,20 +252,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode?> {
|
||||
val tapApplication = application as TapApplication
|
||||
val featureToggle = tapApplication.darkThemeFeatureToggle
|
||||
|
||||
return if (featureToggle.isDarkThemeEnabled) {
|
||||
tapApplication.getAppThemeModeUseCase()
|
||||
.map { maybeMode ->
|
||||
maybeMode.getOrElse { AppThemeMode.DEFAULT }
|
||||
}
|
||||
.shareIn(
|
||||
scope = lifecycleScope + Dispatchers.IO,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
|
||||
)
|
||||
} else {
|
||||
MutableStateFlow(AppThemeMode.FORCE_LIGHT)
|
||||
}
|
||||
return tapApplication.getAppThemeModeUseCase()
|
||||
.map { maybeMode ->
|
||||
maybeMode.getOrElse { AppThemeMode.DEFAULT }
|
||||
}
|
||||
.shareIn(
|
||||
scope = lifecycleScope + Dispatchers.IO,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -454,7 +454,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
private fun checkForNotificationPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
BuildConfig.DEBUG &&
|
||||
BuildConfig.LOG_ENABLED &&
|
||||
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
|
||||
|
||||
class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() {
|
||||
|
||||
override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
|
||||
if (v is ComposeView) return
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import com.orhanobut.logger.AndroidLogAdapter
|
|||
import com.orhanobut.logger.Logger
|
||||
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.analytics.filter.OneTimeEventFilter
|
||||
|
|
@ -30,23 +28,22 @@ import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
|||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.features.tester.api.TesterFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
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
|
||||
|
|
@ -59,24 +56,11 @@ 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.tasks.product.DerivationsFinder
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.tokens.UserTokensStorageService
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
||||
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.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.details.DarkThemeFeatureToggle
|
||||
import com.tangem.tap.features.details.featuretoggles.DetailsFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -95,47 +79,8 @@ lateinit var activityResultCaller: ActivityResultCaller
|
|||
lateinit var preferencesStorage: PreferencesDataSource
|
||||
lateinit var walletConnectRepository: WalletConnectRepository
|
||||
lateinit var shopService: TangemShopService
|
||||
internal lateinit var userTokensRepository: UserTokensRepository
|
||||
internal lateinit var derivationsFinder: DerivationsFinder
|
||||
|
||||
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
|
||||
private val walletManagersRepository by lazy {
|
||||
WalletManagersRepository.provideDefaultImplementation(
|
||||
walletManagerFactory = WalletManagerFactory(
|
||||
config = store.state.globalState.configManager
|
||||
?.config
|
||||
?.blockchainSdkConfig
|
||||
?: BlockchainSdkConfig(),
|
||||
),
|
||||
)
|
||||
}
|
||||
private val walletAmountsRepository by lazy {
|
||||
WalletAmountsRepository.provideDefaultImplementation(
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
)
|
||||
}
|
||||
val walletStoresManager by lazy {
|
||||
WalletStoresManager.provideDefaultImplementation(
|
||||
userTokensRepository = userTokensRepository,
|
||||
walletStoresRepository = walletStoresRepository,
|
||||
walletManagersRepository = walletManagersRepository,
|
||||
walletAmountsRepository = walletAmountsRepository,
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
val walletCurrenciesManager by lazy {
|
||||
WalletCurrenciesManager.provideDefaultImplementation(
|
||||
userTokensRepository = userTokensRepository,
|
||||
walletStoresRepository = walletStoresRepository,
|
||||
walletManagersRepository = walletManagersRepository,
|
||||
walletAmountsRepository = walletAmountsRepository,
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
val totalFiatBalanceCalculator by lazy {
|
||||
TotalFiatBalanceCalculator.provideDefaultImplementation()
|
||||
}
|
||||
|
||||
@HiltAndroidApp
|
||||
internal class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
|
|
@ -161,18 +106,12 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var preferencesDataSource: PreferencesDataSource
|
||||
|
||||
@Inject
|
||||
lateinit var walletFeatureToggles: WalletFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnect2Repository: WalletConnect2Repository
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
|
||||
|
||||
// @Inject
|
||||
// lateinit var learn2earnInteractor: Learn2earnInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var manageTokensFeatureToggles: ManageTokensFeatureToggles
|
||||
|
||||
|
|
@ -200,12 +139,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var balanceHidingRepository: BalanceHidingRepository
|
||||
|
||||
@Inject
|
||||
lateinit var detailsFeatureToggles: DetailsFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var darkThemeFeatureToggle: DarkThemeFeatureToggle
|
||||
|
||||
@Inject
|
||||
lateinit var userTokensStore: UserTokensStore
|
||||
|
||||
|
|
@ -220,6 +153,12 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
@Inject
|
||||
lateinit var oneTimeEventFilter: OneTimeEventFilter
|
||||
|
||||
@Inject
|
||||
lateinit var derivationsRepository: DerivationsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var testerFeatureToggles: TesterFeatureToggles
|
||||
// endregion Injected
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -227,7 +166,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
store = createReduxStore()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (BuildConfig.LOG_ENABLED) {
|
||||
Logger.addLogAdapter(AndroidLogAdapter(TimberFormatStrategy()))
|
||||
Timber.plant(
|
||||
object : Timber.DebugTree() {
|
||||
|
|
@ -250,7 +189,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
runBlocking {
|
||||
initUserWalletsListManager()
|
||||
featureTogglesManager.init()
|
||||
// learn2earnInteractor.init()
|
||||
}
|
||||
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
||||
|
|
@ -266,23 +204,12 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
)
|
||||
}
|
||||
|
||||
val userTokensStorageService = UserTokensStorageService.init(context = this)
|
||||
userTokensRepository = UserTokensRepository.init(
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
storageService = userTokensStorageService,
|
||||
)
|
||||
derivationsFinder = DerivationsFinder(
|
||||
legacyTokensStore = userTokensStorageService,
|
||||
newTokensStore = userTokensStore,
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
initTopUpController()
|
||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
||||
}
|
||||
|
||||
|
|
@ -292,10 +219,8 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
middleware = AppState.getMiddleware(),
|
||||
state = AppState(
|
||||
daggerGraphState = DaggerGraphState(
|
||||
assetReader = assetReader,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
walletConnectRepository = walletConnect2Repository,
|
||||
walletConnectSessionsRepository = walletConnectSessionsRepository,
|
||||
manageTokensFeatureToggles = manageTokensFeatureToggles,
|
||||
|
|
@ -307,26 +232,15 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
currenciesRepository = currenciesRepository,
|
||||
appThemeModeRepository = appThemeModeRepository,
|
||||
balanceHidingRepository = balanceHidingRepository,
|
||||
detailsFeatureToggles = detailsFeatureToggles,
|
||||
walletsRepository = walletsRepository,
|
||||
sendFeatureToggles = sendFeatureToggles,
|
||||
derivationsRepository = derivationsRepository,
|
||||
testerFeatureToggles = testerFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun initTopUpController() {
|
||||
val topUpController = TopUpController(
|
||||
scanResponseProvider = {
|
||||
store.state.globalState.scanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
},
|
||||
walletStoresManagerProvider = { walletStoresManager },
|
||||
topupWalletStorage = preferencesStorage.toppedUpWalletStorage,
|
||||
)
|
||||
store.dispatch(GlobalAction.SetTopUpController(topUpController))
|
||||
}
|
||||
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
return createCoilImageLoader(
|
||||
context = this,
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ import android.content.Context
|
|||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.ui.SimpleAlertDialog
|
||||
import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
|
||||
import com.tangem.tap.common.ui.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.ui.wallet.CurrencySelectionDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -50,13 +45,14 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
if (dialog != null) return
|
||||
|
||||
dialog = when (state.dialog) {
|
||||
is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.SimpleOkErrorDialog -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.SimpleOkWarningDialog -> SimpleOkDialog.create(state.dialog, context)
|
||||
is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(
|
||||
context,
|
||||
state.dialog.data,
|
||||
)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
|
|
@ -124,6 +120,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
onReject = state.dialog.onReject,
|
||||
)
|
||||
}
|
||||
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
|
|
@ -132,11 +129,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
context = context,
|
||||
cardId = state.dialog.cardId,
|
||||
)
|
||||
is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context)
|
||||
is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context)
|
||||
is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
|
||||
is AppDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
|
||||
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
|
||||
message = context.getString(
|
||||
state.dialog.messageRes,
|
||||
|
|
@ -145,15 +138,13 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
),
|
||||
context = context,
|
||||
)
|
||||
is WalletDialog.RemoveWalletDialog -> SimpleCancelableAlertDialog.create(
|
||||
is AppDialog.RemoveWalletDialog -> SimpleCancelableAlertDialog.create(
|
||||
title = context.getString(state.dialog.titleRes, state.dialog.currencyTitle),
|
||||
messageRes = state.dialog.messageRes,
|
||||
context = context,
|
||||
primaryButtonRes = state.dialog.primaryButtonRes,
|
||||
primaryButtonAction = state.dialog.onOk,
|
||||
)
|
||||
is WalletDialog.RussianCardholdersWarningDialog ->
|
||||
RussianCardholdersWarningBottomSheetDialog(context, state.dialog.data)
|
||||
else -> null
|
||||
}
|
||||
dialog?.show()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class GlobalLayoutStateHandler<T : View>(
|
|||
if (attachImmediately) attach()
|
||||
}
|
||||
|
||||
fun attach() {
|
||||
private fun attach() {
|
||||
if (isAttached) {
|
||||
Timber.d("Already attached")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -11,20 +11,6 @@ fun postUi(ms: Long = 0, func: Runnable) {
|
|||
if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms)
|
||||
}
|
||||
|
||||
fun postBackground(ms: Long = 0, func: Runnable) {
|
||||
if (ms == 0L) backgroundHandler.post { func.run() } else backgroundHandler.postDelayed(func, ms)
|
||||
}
|
||||
|
||||
fun postUiDelayBg(ms: Long, func: Runnable) {
|
||||
backgroundHandler.postDelayed({ uiHandler.post(func) }, ms)
|
||||
}
|
||||
|
||||
fun post(ms: Long = 0, func: Runnable) {
|
||||
if (ms == 0L) {
|
||||
func.run()
|
||||
} else {
|
||||
val currentLooper = Looper.myLooper() ?: return
|
||||
|
||||
Handler(currentLooper).postDelayed(func, ms)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class ShimmerRecyclerAdapter(
|
||||
private val viewHolderViewFactory: (ViewGroup) -> ViewGroup,
|
||||
) : ListAdapter<ShimmerData, ShimmerVH>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH {
|
||||
return ShimmerVH(viewHolderViewFactory.invoke(parent))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ShimmerVH, position: Int) {}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<ShimmerData>() {
|
||||
override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
|
||||
override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup)
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
data class ShimmerData(private val any: String = "")
|
||||
|
|
@ -8,11 +8,8 @@ import androidx.appcompat.widget.LinearLayoutCompat
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -22,18 +19,6 @@ object TestActions {
|
|||
|
||||
// It used only for the test actions in debug or debug_beta builds
|
||||
var testAmountInjectionForWalletManagerEnabled = false
|
||||
|
||||
/**
|
||||
* @param isTestView - true must be used if you want to show or hide your view depends on BuildConfig
|
||||
*/
|
||||
fun initFor(view: View, actions: List<TestAction>, isTestView: Boolean = false) {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
|
||||
|
||||
view.setOnClickListener {
|
||||
store.dispatchDialogShow(AppDialog.TestActionsDialog(actions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestAction = Pair<String, () -> Unit>
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ class AnalyticsFactory {
|
|||
filters.add(filter)
|
||||
}
|
||||
|
||||
fun addParamsInterceptor(interceptor: ParamsInterceptor) {
|
||||
interceptors.add(interceptor)
|
||||
}
|
||||
|
||||
fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
|
||||
builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
|
||||
filters.forEach { analytics.addFilter(it) }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ParamCardCurrencyConverter : Converter<CardTypesResolver, AnalyticsParam.CardCurrency?> {
|
||||
class ParamCardCurrencyConverter : Converter<CardTypesResolver, CoreAnalyticsParam.WalletType?> {
|
||||
|
||||
override fun convert(value: CardTypesResolver): AnalyticsParam.CardCurrency? {
|
||||
if (value.isMultiwalletAllowed()) return AnalyticsParam.CardCurrency.MultiCurrency
|
||||
override fun convert(value: CardTypesResolver): CoreAnalyticsParam.WalletType? {
|
||||
if (value.isMultiwalletAllowed()) return CoreAnalyticsParam.WalletType.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
|
|
@ -21,6 +22,6 @@ class ParamCardCurrencyConverter : Converter<CardTypesResolver, AnalyticsParam.C
|
|||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return AnalyticsParam.CardCurrency.SingleCurrency(type)
|
||||
return CoreAnalyticsParam.WalletType.SingleCurrency(type.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TopUpEventConverter : Converter<Pair<UserWalletId, CardTypesResolver>, Basic.ToppedUp?> {
|
||||
|
||||
override fun convert(value: Pair<UserWalletId, CardTypesResolver>): Basic.ToppedUp? {
|
||||
val (userWalletId, resolver) = value
|
||||
val paramCardCurrency = ParamCardCurrencyConverter().convert(resolver) ?: return null
|
||||
|
||||
return Basic.ToppedUp(userWalletId, paramCardCurrency)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,42 +6,36 @@ import com.tangem.tap.features.details.redux.SecurityOption
|
|||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CurrencyType(val value: String) {
|
||||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Currency(currency: com.tangem.tap.domain.model.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.code)
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
// MultiCurrency or CurrencyType
|
||||
sealed class CardCurrency(val value: String) {
|
||||
object MultiCurrency : CardCurrency(value = "Multicurrency")
|
||||
class SingleCurrency(type: CurrencyType) : CardCurrency(type.value)
|
||||
}
|
||||
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
object CustomToken : CardBalanceState("Custom Token")
|
||||
object BlockchainError : CardBalanceState("Blockchain Error")
|
||||
object NoRate : CardBalanceState("No Rate")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Disliked : RateApp("Disliked")
|
||||
object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
sealed class OnOffState(val value: String) {
|
||||
|
||||
object On : OnOffState("On")
|
||||
object Off : OnOffState("Off")
|
||||
|
||||
companion object {
|
||||
|
||||
operator fun invoke(value: Boolean): OnOffState = if (value) On else Off
|
||||
}
|
||||
}
|
||||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
object Passcode : UserCode("Passcode")
|
||||
}
|
||||
|
||||
sealed class SecurityMode(val value: String) {
|
||||
|
|
@ -78,62 +72,6 @@ sealed class AnalyticsParam {
|
|||
object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
}
|
||||
|
||||
sealed class ScannedFrom(val value: String) {
|
||||
object Introduction : ScannedFrom("Introduction")
|
||||
object Main : ScannedFrom("Main")
|
||||
object SignIn : ScannedFrom("Sign In")
|
||||
object MyWallets : ScannedFrom("My Wallets")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
data class Send(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
) : TxSentFrom("Send"), TxData
|
||||
|
||||
data class Swap(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
) : TxSentFrom("Swap"), TxData
|
||||
|
||||
data class Approve(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
val permissionType: String,
|
||||
) : TxSentFrom("Approve"), TxData
|
||||
|
||||
object WalletConnect : TxSentFrom("WalletConnect")
|
||||
object Sell : TxSentFrom("Sell")
|
||||
}
|
||||
|
||||
sealed interface TxData {
|
||||
val blockchain: String
|
||||
val token: String
|
||||
val feeType: FeeType
|
||||
}
|
||||
|
||||
sealed class FeeType(val value: String) {
|
||||
object Fixed : FeeType("Fixed")
|
||||
object Min : FeeType("Min")
|
||||
object Normal : FeeType("Normal")
|
||||
object Max : FeeType("Max")
|
||||
|
||||
companion object {
|
||||
fun fromString(feeType: String): FeeType {
|
||||
return when (feeType) {
|
||||
Min.value -> Min
|
||||
Normal.value -> Normal
|
||||
Max.value -> Max
|
||||
Fixed.value -> Fixed
|
||||
else -> Fixed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType(value = "Private Key")
|
||||
object NewSeed : WalletCreationType(value = "New Seed")
|
||||
|
|
|
|||
|
|
@ -12,23 +12,14 @@ sealed class MainScreen(
|
|||
|
||||
class ScreenOpened : MainScreen("Screen opened")
|
||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
|
||||
event = "Enable Biometric",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen(
|
||||
event = "Main Currency Changed",
|
||||
params = mapOf("Currency Type" to currencyType.value),
|
||||
)
|
||||
|
||||
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
event = "Notice - Rate The App Button Tapped",
|
||||
params = mapOf("Result" to result.value),
|
||||
)
|
||||
|
||||
class NoticeBackupYourWalletTapped : MainScreen("Notice - Backup Your Wallet Tapped")
|
||||
class NoticeScanYourCardTapped : MainScreen("Notice - Scan Your Card Tapped")
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
sealed class MyWallets(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("My Wallets", event, params) {
|
||||
|
||||
class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
|
||||
|
||||
sealed class Button {
|
||||
class ScanNewCard : MyWallets(event = "Button - Scan New Card")
|
||||
class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
|
||||
class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
|
||||
class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
|
||||
class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
sealed class Portfolio(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Portfolio", event, params) {
|
||||
|
||||
class Refreshed : Portfolio("Refreshed")
|
||||
|
||||
class ButtonManageTokens : Portfolio("Button - Manage Tokens")
|
||||
|
||||
class TokenTapped : Portfolio("Token is Tapped")
|
||||
|
||||
class OrganizeTokens : Portfolio("Button - Organize Tokens")
|
||||
}
|
||||
|
|
@ -89,5 +89,12 @@ sealed class Settings(
|
|||
event = "App Theme Switched",
|
||||
params = mapOf("State" to theme.value),
|
||||
)
|
||||
|
||||
object EnableBiometrics : AppSettings(event = "Notice - Enable Biometric")
|
||||
|
||||
class HideBalanceChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
||||
event = "Hide Balance Changed",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,50 +12,6 @@ sealed class Token(
|
|||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class Refreshed : Token("Token", "Refreshed")
|
||||
class ButtonExplore : Token("Token", "Button - Explore")
|
||||
|
||||
class ButtonRemoveToken(type: CurrencyType) : Token(
|
||||
"Token",
|
||||
"Button - Remove Token",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonBuy(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Buy",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSell(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Sell",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonExchange(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Exchange",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSend(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Send",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class Bought(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Token Bought",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
object ShowWalletAddress : Token(
|
||||
category = "Token",
|
||||
event = "Button - Show the Wallet Address",
|
||||
)
|
||||
|
||||
sealed class Receive(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.common.analytics.handlers.amplitude
|
|||
import android.app.Application
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import com.tangem.utils.converter.Converter
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.analytics.handlers.firebase
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
|
|
@ -29,8 +28,4 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
|
|||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
|
||||
|
||||
companion object {
|
||||
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ class CardContextInterceptor(
|
|||
ProductType.Wallet2 -> "Wallet 2.0"
|
||||
ProductType.Ring -> "Ring"
|
||||
ProductType.Start2Coin -> "Start2Coin"
|
||||
ProductType.Visa -> "VISA"
|
||||
else -> if (DemoHelper.isDemoCard(scanResponse)) {
|
||||
if (DemoHelper.isTestDemoCard(scanResponse)) {
|
||||
"Demo Test"
|
||||
|
|
|
|||
|
|
@ -1,207 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.topup
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.model.DataSourceTopupInfo
|
||||
import com.tangem.data.source.preferences.storage.ToppedUpWalletStorage
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.converters.TopUpEventConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.utils.extensions.copy
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TopUpController(
|
||||
var scanResponseProvider: (() -> ScanResponse?)? = null,
|
||||
var walletStoresManagerProvider: (() -> WalletStoresManager)? = null,
|
||||
private val topupWalletStorage: ToppedUpWalletStorage,
|
||||
) : WalletCurrenciesManager.Listener {
|
||||
|
||||
private var hadMissedDerivations: Boolean = false
|
||||
private val addedCurrencies = mutableListOf<Currency>()
|
||||
|
||||
override fun didUpdate(userWallet: UserWallet, currency: Currency) {
|
||||
tryToSend()
|
||||
}
|
||||
|
||||
override fun willCurrenciesAdd(userWallet: UserWallet, currenciesToAdd: List<Currency>) {
|
||||
addedCurrencies.addAll(currenciesToAdd.distinct())
|
||||
}
|
||||
|
||||
fun walletStoresChanged(walletStores: List<WalletStoreModel>) {
|
||||
val missedDerivations = walletStores
|
||||
.flatMap { it.walletsData }
|
||||
.map { it.status }
|
||||
.filterIsInstance<WalletDataModel.MissedDerivation>()
|
||||
|
||||
hadMissedDerivations = missedDerivations.isNotEmpty()
|
||||
}
|
||||
|
||||
fun scanToGetDerivations() {
|
||||
hadMissedDerivations = true
|
||||
}
|
||||
|
||||
fun addMissingDerivations(blockchains: List<BlockchainNetwork>) {
|
||||
hadMissedDerivations = blockchains.isNotEmpty()
|
||||
}
|
||||
|
||||
fun totalBalanceStateChanged(totalFiatBalance: TotalFiatBalance) {
|
||||
if (totalFiatBalance is TotalFiatBalance.Loaded) tryToSend()
|
||||
}
|
||||
|
||||
fun loadDataSuccess() {
|
||||
tryToSend()
|
||||
}
|
||||
|
||||
private fun tryToSend() {
|
||||
if (hadMissedDerivations) return
|
||||
val scanResponse = scanResponseProvider?.invoke() ?: return
|
||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() ?: return
|
||||
val walletStoresManager = walletStoresManagerProvider?.invoke() ?: return
|
||||
|
||||
scope.launch {
|
||||
val walletDataModels = walletStoresManager.getSync(userWalletId).flatMap { it.walletsData }
|
||||
if (walletDataModels.isEmpty()) return@launch
|
||||
|
||||
val isCorrectStatus = walletDataModels.any {
|
||||
it.status is WalletDataModel.Loading ||
|
||||
it.status is WalletDataModel.NoAccount ||
|
||||
it.status is WalletDataModel.Unreachable ||
|
||||
it.status is WalletDataModel.MissedDerivation ||
|
||||
it.status.isErrorStatus
|
||||
}
|
||||
if (isCorrectStatus) return@launch
|
||||
|
||||
val isToppedUpInPast = findToppedUpCurrenciesInPast(walletDataModels).isNotEmpty()
|
||||
if (isToppedUpInPast) {
|
||||
val newWalletInfo = DataSourceTopupInfo(
|
||||
walletId = userWalletId.stringValue,
|
||||
cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full,
|
||||
)
|
||||
topupWalletStorage.save(newWalletInfo)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val cardBalanceState = BalanceCalculator(walletDataModels).calculate().toCardBalanceState()
|
||||
send(userWalletId, cardBalanceState, scanResponse.cardTypesResolver)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A UserWalletId registration should be after creating wallets
|
||||
*/
|
||||
fun registerEmptyWallet(scanResponse: ScanResponse) {
|
||||
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
|
||||
topupWalletStorage.save(
|
||||
DataSourceTopupInfo(
|
||||
walletId = it.stringValue,
|
||||
cardBalanceState = DataSourceTopupInfo.CardBalanceState.Empty,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun send(scanResponse: ScanResponse, cardBalanceState: AnalyticsParam.CardBalanceState) {
|
||||
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
|
||||
send(it, cardBalanceState, scanResponse.cardTypesResolver)
|
||||
}
|
||||
}
|
||||
|
||||
fun send(
|
||||
userWalletId: UserWalletId,
|
||||
cardBalanceState: AnalyticsParam.CardBalanceState,
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
) {
|
||||
val topupInfo = topupWalletStorage.restore(userWalletId.stringValue).guard {
|
||||
val topupInfo = DataSourceTopupInfo(
|
||||
walletId = userWalletId.stringValue,
|
||||
cardBalanceState = when (cardBalanceState) {
|
||||
AnalyticsParam.CardBalanceState.BlockchainError ->
|
||||
DataSourceTopupInfo.CardBalanceState.BlockchainError
|
||||
AnalyticsParam.CardBalanceState.CustomToken ->
|
||||
DataSourceTopupInfo.CardBalanceState.CustomToken
|
||||
AnalyticsParam.CardBalanceState.Empty ->
|
||||
DataSourceTopupInfo.CardBalanceState.Empty
|
||||
AnalyticsParam.CardBalanceState.Full ->
|
||||
DataSourceTopupInfo.CardBalanceState.Full
|
||||
AnalyticsParam.CardBalanceState.NoRate ->
|
||||
DataSourceTopupInfo.CardBalanceState.NoRate
|
||||
},
|
||||
)
|
||||
topupWalletStorage.save(topupInfo)
|
||||
return
|
||||
}
|
||||
|
||||
val isToppedUp = topupInfo.cardBalanceState == DataSourceTopupInfo.CardBalanceState.Full
|
||||
if (isToppedUp) return
|
||||
|
||||
if (cardBalanceState.isToppedUp()) {
|
||||
topupWalletStorage.save(topupInfo.copy(cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full))
|
||||
TopUpEventConverter().convert(value = userWalletId to cardTypesResolver)?.let {
|
||||
Analytics.send(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findToppedUpCurrenciesInPast(walletDataModels: List<WalletDataModel>): List<WalletDataModel> {
|
||||
val currenciesToppedUpInPast = addedCurrencies.copy()
|
||||
.mapNotNull { currency ->
|
||||
val foundCurrencyModel = walletDataModels
|
||||
.find { it.currency == currency }
|
||||
?: return@mapNotNull null
|
||||
|
||||
if (foundCurrencyModel.status.amount.isZero()) null else foundCurrencyModel
|
||||
}
|
||||
addedCurrencies.clear()
|
||||
|
||||
return currenciesToppedUpInPast
|
||||
}
|
||||
|
||||
private fun BigDecimal.toCardBalanceState(): AnalyticsParam.CardBalanceState = when {
|
||||
isZero() -> AnalyticsParam.CardBalanceState.Empty
|
||||
else -> AnalyticsParam.CardBalanceState.Full
|
||||
}
|
||||
|
||||
private fun AnalyticsParam.CardBalanceState.isToppedUp(): Boolean = this == AnalyticsParam.CardBalanceState.Full
|
||||
}
|
||||
|
||||
private interface IBalanceCalculator {
|
||||
fun calculate(): BigDecimal
|
||||
}
|
||||
|
||||
private class BalanceCalculator(
|
||||
private val walletDataModels: List<WalletDataModel>,
|
||||
) : IBalanceCalculator {
|
||||
|
||||
override fun calculate(): BigDecimal {
|
||||
val singleToken = walletDataModels
|
||||
.filter { it.currency.isToken() }
|
||||
.firstOrNull { it.isCardSingleToken }
|
||||
|
||||
val totalAmount = singleToken?.status?.amount
|
||||
?: walletDataModels.calculateTotalCryptoAmount()
|
||||
|
||||
return totalAmount
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.calculateTotalCryptoAmount(): BigDecimal = this
|
||||
.map { it.status.amount }
|
||||
.reduce(BigDecimal::plus)
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun TextAutoSize(
|
||||
text: String,
|
||||
fontSizeRange: FontSizeRange,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
|
||||
val readyToDraw = remember { mutableStateOf(false) }
|
||||
|
||||
val textState = remember { mutableStateOf(text) }
|
||||
if (textState.value != text) {
|
||||
readyToDraw.value = false
|
||||
fontSizeValue.value = fontSizeRange.max.value
|
||||
textState.value = text
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
|
||||
text = text,
|
||||
softWrap = false,
|
||||
style = textStyle,
|
||||
fontSize = fontSizeValue.value.sp,
|
||||
onTextLayout = {
|
||||
if (it.hasVisualOverflow) {
|
||||
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
|
||||
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
||||
fontSizeValue.value = fontSizeRange.min.value
|
||||
readyToDraw.value = true
|
||||
} else {
|
||||
fontSizeValue.value = nextFontSizeValue * 0.8f
|
||||
}
|
||||
} else {
|
||||
readyToDraw.value = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class FontSizeRange(
|
||||
val min: TextUnit,
|
||||
val max: TextUnit,
|
||||
val step: TextUnit = DEFAULT_TEXT_STEP,
|
||||
) {
|
||||
init {
|
||||
require(min < max) { "min should be less than max, $this" }
|
||||
require(step.value > 0) { "step should be greater than 0, $this" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_TEXT_STEP = 1.sp
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.ripple.LocalRippleTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
|
||||
/**
|
||||
* Used for disable ripple if button is enable = false
|
||||
*/
|
||||
@Composable
|
||||
fun ToggledRippleTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
|
||||
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
|
||||
CompositionLocalProvider(theme) { content() }
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.tangem.domain.common.util.ValueDebouncer
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* This is an empty compose view. It just remember the ValueDebouncer inside of itself.
|
||||
*/
|
||||
@Composable
|
||||
fun <T> valueDebouncerAsState(
|
||||
initialValue: T,
|
||||
onValueChange: (T) -> Unit,
|
||||
debounce: Long = 600,
|
||||
onEmitValueReceive: (T) -> Unit = {},
|
||||
): ValueDebouncer<T> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = { emitValue ->
|
||||
emitValue?.let { onEmitValueReceive(it) }
|
||||
},
|
||||
onValueChanged = { changedValue ->
|
||||
changedValue?.let { onValueChange(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> valueDebouncerNullableAsState(
|
||||
initialValue: T?,
|
||||
onValueChange: (T?) -> Unit,
|
||||
debounce: Long = 400,
|
||||
onEmitValueReceive: (T?) -> Unit = {},
|
||||
): ValueDebouncer<T?> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = onEmitValueReceive,
|
||||
onValueChanged = onValueChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun ErrorView(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current) {
|
||||
Text(
|
||||
text,
|
||||
color = MaterialTheme.colors.error,
|
||||
modifier = modifier,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ErrorViewTest() {
|
||||
Box(Modifier.padding(16.dp)) {
|
||||
ErrorView(text = "Some error description")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.ripple.RippleAlpha
|
||||
import androidx.compose.material.ripple.RippleTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NoRippleTheme : RippleTheme {
|
||||
@Composable
|
||||
override fun defaultColor() = Color.Unspecified
|
||||
|
||||
@Composable
|
||||
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.text.isDigitsOnly
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun PinCodeWidget(
|
||||
config: PinViewConfig = tangemPinConfig,
|
||||
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
val rTextFieldValue = remember { mutableStateOf(TextFieldValue("")) }
|
||||
val indexedSymbols: List<String?> = createPinSymbolsList(config.pinsCount, rTextFieldValue.value.text)
|
||||
|
||||
fun isLastSymbolEntered(): Boolean = rTextFieldValue.value.text.length == config.pinsCount
|
||||
|
||||
fun handleOnTextFieldValueChanged(value: TextFieldValue) {
|
||||
if (!value.text.isDigitsOnly()) return
|
||||
|
||||
if (value.text.length <= config.pinsCount) {
|
||||
rTextFieldValue.value = value
|
||||
onPinChange(value.text, isLastSymbolEntered())
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = config.modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
focusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Row {
|
||||
for (index in 0 until config.pinsCount) {
|
||||
PinElement(
|
||||
config = config,
|
||||
pinSymbol = indexedSymbols[index] ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.alpha(0f)
|
||||
.size(1.dp)
|
||||
.align(Alignment.Center)
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
imeAction = if (isLastSymbolEntered()) ImeAction.Done else ImeAction.Next,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { keyboardController?.hide() },
|
||||
),
|
||||
value = rTextFieldValue.value,
|
||||
onValueChange = ::handleOnTextFieldValueChanged,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinElement(config: PinViewConfig, pinSymbol: String) {
|
||||
Box(Modifier.padding(config.pinBoxPadding)) {
|
||||
Box(config.pinBoxModifier) {
|
||||
Text(
|
||||
text = pinSymbol,
|
||||
modifier = config.pinTextModifier.align(Alignment.Center),
|
||||
style = config.pinsTextStyle ?: LocalTextStyle.current,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
|
||||
try {
|
||||
text[it].toString()
|
||||
} catch (ex: IndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
data class PinViewConfig(
|
||||
val modifier: Modifier = Modifier,
|
||||
val pinBoxModifier: Modifier = Modifier,
|
||||
val pinBoxPadding: Dp = 0.dp,
|
||||
val pinTextModifier: Modifier = Modifier,
|
||||
val pinsCount: Int = 4,
|
||||
val pinsTextStyle: TextStyle? = null,
|
||||
)
|
||||
|
||||
private val tangemPinConfig = PinViewConfig(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(),
|
||||
pinBoxModifier = Modifier
|
||||
.width(42.dp)
|
||||
.height(56.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFF0F0F0)),
|
||||
pinBoxPadding = 6.dp,
|
||||
pinTextModifier = Modifier,
|
||||
pinsCount = 4,
|
||||
pinsTextStyle = TextStyle(
|
||||
fontWeight = FontWeight(500),
|
||||
fontSize = 24.sp,
|
||||
),
|
||||
)
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PinCodeWidgetPreview() {
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
|
||||
PinCodeWidget(tangemPinConfig)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.common.module.ModuleMessage
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun AddCustomTokenWarning(warning: ModuleMessage, converter: ModuleMessageConverter, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.warning_warning),
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_warning),
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = converter.convert(warning).message,
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun copyToClipboard(value: Any, label: String = "") {
|
||||
LocalContext.current.copyToClipboard(value, label)
|
||||
}
|
||||
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
return LocalContext.current.getFromClipboard(default)
|
||||
}
|
||||
|
|
@ -14,6 +14,4 @@ fun Dp.toPx(): Float {
|
|||
return with(LocalDensity.current) { currentDp.toPx() }
|
||||
}
|
||||
|
||||
fun DpSize.halfWidth(): Dp = this.width / 2
|
||||
|
||||
fun DpSize.halfHeight(): Dp = this.height / 2
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import com.tangem.tap.common.extensions.hideKeyboard
|
||||
|
||||
@Composable
|
||||
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
|
||||
require(loadMoreThreshold >= 0)
|
||||
val shouldLoadMore by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
?: return@derivedStateOf false
|
||||
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - loadMoreThreshold
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMore) {
|
||||
if (shouldLoadMore) onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LazyListState.HideKeyboardOnScroll() {
|
||||
if (isScrollInProgress) {
|
||||
LocalView.current.hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <T> MutableState<List<T>>.addAndNotify(value: T) {
|
||||
this.value = this.value.toMutableList().apply { add(value) }
|
||||
}
|
||||
|
||||
fun <T> MutableState<List<T>>.removeAndNotify(value: T) {
|
||||
this.value = this.value.toMutableList().apply { remove(value) }
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.painter.Painter
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
|
|
@ -17,8 +16,5 @@ fun Painter.dpSize(): DpSize = DpSize(
|
|||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
open class Button(val enabled: Boolean)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
data class FiatCurrency(
|
||||
val code: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
) {
|
||||
val displayName: String
|
||||
get() = "${this.name} (${this.code}) - ${this.symbol}"
|
||||
|
||||
companion object {
|
||||
val Default = FiatCurrency(
|
||||
symbol = "$",
|
||||
code = "USD",
|
||||
name = "US Dollar",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Done, Error }
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.res.AssetManager
|
||||
|
||||
fun AssetManager.readJsonFileToString(fileName: String): String =
|
||||
this.open("$fileName.json").bufferedReader().readText()
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
|
|
@ -9,9 +8,4 @@ fun Bitmap.toByteArray(): ByteArray {
|
|||
val stream = ByteArrayOutputStream()
|
||||
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
|
||||
return stream.toByteArray()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun ByteArray.toBitmap(): Bitmap {
|
||||
return BitmapFactory.decodeByteArray(this, 0, this.size)
|
||||
}
|
||||
|
|
@ -7,10 +7,6 @@ import android.net.*
|
|||
import androidx.annotation.*
|
||||
import androidx.core.content.*
|
||||
|
||||
fun Context.isPermissionGranted(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uri to any resource type via given Resource Instance
|
||||
* @param resId - resource id
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import coil.request.ImageRequest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply {
|
||||
data(any)
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ImageView.setDrawable(@DrawableRes resId: Int) {
|
||||
setImageDrawable(context.getDrawableCompat(resId))
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRe
|
|||
import com.tangem.tap.features.details.ui.details.DetailsFragment
|
||||
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
|
||||
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
|
||||
import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
|
||||
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
|
||||
import com.tangem.tap.features.home.HomeFragment
|
||||
|
|
@ -27,9 +26,6 @@ import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
|||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
||||
import com.tangem.tap.features.welcome.ui.WelcomeFragment
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -132,16 +128,9 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.OnboardingTwins -> TwinsCardsFragment()
|
||||
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
|
||||
AppScreen.Wallet -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::walletFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::walletRouter)
|
||||
.getEntryFragment()
|
||||
} else {
|
||||
WalletFragment()
|
||||
}
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::walletRouter)
|
||||
.getEntryFragment()
|
||||
}
|
||||
AppScreen.Send -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
|
|
@ -176,24 +165,20 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
}
|
||||
AppScreen.AddCustomToken -> AddCustomTokenFragment()
|
||||
AppScreen.WalletDetails -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::walletFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::tokenDetailsRouter)
|
||||
.getEntryFragment()
|
||||
} else {
|
||||
WalletDetailsFragment()
|
||||
}
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::tokenDetailsRouter)
|
||||
.getEntryFragment()
|
||||
}
|
||||
AppScreen.WalletConnectSessions -> WalletConnectFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
AppScreen.QrScanning -> {
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::qrScanningRouter)
|
||||
.getEntryFragment()
|
||||
}
|
||||
AppScreen.ReferralProgram -> ReferralFragment()
|
||||
AppScreen.Swap -> SwapFragment()
|
||||
AppScreen.Welcome -> WelcomeFragment()
|
||||
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
|
||||
AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment()
|
||||
AppScreen.AppCurrencySelector -> AppCurrencySelectorFragment()
|
||||
AppScreen.ModalNotification -> ModalNotificationBottomSheetFragment()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.tangem.common.extensions.isZero
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
// TODO: move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
|
|
@ -29,105 +23,6 @@ fun BigDecimal.toFormattedString(
|
|||
return df.format(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* To formatted crypto currency string
|
||||
* Specific method because there is no crypto currency codes in Locale
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCryptoCurrencyString(
|
||||
decimals: Int,
|
||||
currency: String,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
val decimalsForRounding = if (limitNumberOfDecimals) {
|
||||
if (decimals > 8) 8 else decimals
|
||||
} else {
|
||||
decimals
|
||||
}
|
||||
try {
|
||||
val locale = Locale.getDefault()
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale)
|
||||
// first create currency instance for "USD" with Locale default to replace currency to crypto later
|
||||
Currency.getInstance("USD")?.let { currencyTmp ->
|
||||
formatter.currency = currencyTmp
|
||||
formatter.maximumFractionDigits = decimalsForRounding
|
||||
formatter.minimumFractionDigits = 0
|
||||
formatter.isGroupingUsed = true
|
||||
formatter.roundingMode = roundingMode
|
||||
// cause formatter created for USD, replace Currency with Crypto symbol on right by Locale place
|
||||
return formatter.format(this).replace(currencyTmp.getSymbol(locale), "$currency ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
// if something went wrong - use old way to format
|
||||
val formattedAmount = this.toFormattedString(
|
||||
decimals = decimalsForRounding,
|
||||
roundingMode = roundingMode,
|
||||
locale = Locale.getDefault(),
|
||||
)
|
||||
return "$formattedAmount $currency "
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String {
|
||||
try {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
|
||||
Currency.getInstance(fiatCode)?.let { currency ->
|
||||
formatter.currency = currency
|
||||
formatter.maximumFractionDigits = 2
|
||||
formatter.roundingMode = RoundingMode.HALF_UP
|
||||
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
val value = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
return "$value $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
fiatCode: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.toFormattedFiatValue(
|
||||
fiatCurrencyName = fiatCurrencyName,
|
||||
fiatCode = fiatCode,
|
||||
formatWithSpaces = formatWithSpaces,
|
||||
)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.setScale(2, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
fiatCode: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
try {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
|
||||
Currency.getInstance(fiatCode)?.let { currency ->
|
||||
formatter.currency = currency
|
||||
formatter.maximumFractionDigits = 2
|
||||
formatter.roundingMode = RoundingMode.HALF_UP
|
||||
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
|
||||
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
|
||||
return " $fiatValue $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
// 0.00 -> 0.00
|
||||
|
|
@ -146,70 +41,4 @@ fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = Roundin
|
|||
return this.setScale(scale() - precision() + precision, roundingMode)
|
||||
}
|
||||
|
||||
fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 1
|
||||
fun BigDecimal.isNegative(): Boolean = this.compareTo(BigDecimal.ZERO) == -1
|
||||
fun BigDecimal.isGreaterThan(value: BigDecimal): Boolean = this.compareTo(value) == 1
|
||||
fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1
|
||||
|
||||
fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
|
||||
val compareResult = this.compareTo(value)
|
||||
return compareResult == 1 || compareResult == 0
|
||||
}
|
||||
|
||||
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
|
||||
val compareResult = this.compareTo(value)
|
||||
return compareResult == -1 || compareResult == 0
|
||||
}
|
||||
|
||||
fun BigDecimal.formatAmountAsSpannedString(
|
||||
currencySymbol: String,
|
||||
reminderPartSizeProportion: Float = 0.7f,
|
||||
): SpannedString {
|
||||
val amount = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
val integer = amount.substringBefore('.')
|
||||
val reminder = amount.substringAfter('.')
|
||||
|
||||
// test formatter log Log.e("TEST ", BigDecimal("1234567890987654321.1234567890987654321").formatWithSpaces())
|
||||
|
||||
return buildSpannedString {
|
||||
append(integer)
|
||||
append('.')
|
||||
append(
|
||||
"$reminder $currencySymbol",
|
||||
RelativeSizeSpan(reminderPartSizeProportion),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.formatWithSpaces(): String {
|
||||
val str = this.toString()
|
||||
var integerStr = str.substringBefore('.')
|
||||
val reminderStr = str.substringAfter('.')
|
||||
val packets = arrayListOf<String>()
|
||||
|
||||
var index: Int = integerStr.length
|
||||
while (0 < index) {
|
||||
if (index <= 3) {
|
||||
packets.add(integerStr)
|
||||
break
|
||||
}
|
||||
index -= 3
|
||||
packets.add(integerStr.substring(startIndex = index))
|
||||
integerStr = integerStr.substring(startIndex = 0, endIndex = index)
|
||||
}
|
||||
|
||||
return buildString {
|
||||
packets.reversed().forEachIndexed { index, packet ->
|
||||
append(packet)
|
||||
if (index != packets.lastIndex) append(' ')
|
||||
}
|
||||
if (reminderStr.isNotBlank()) {
|
||||
append('.')
|
||||
append(reminderStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun BigDecimal.isPositive(): Boolean = this.signum() == 1
|
||||
|
|
@ -42,16 +42,8 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
|||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||
}
|
||||
|
||||
suspend fun Store<AppState>.onUserWalletSelected(
|
||||
userWallet: UserWallet,
|
||||
refresh: Boolean = false,
|
||||
sendAnalyticsEvent: Boolean = false,
|
||||
) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||
dispatchOnMain(GlobalAction.ShowToastNotification(resId))
|
||||
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean = false) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchErrorNotification(error: TapError) {
|
||||
|
|
|
|||
|
|
@ -1,41 +1,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import java.util.*
|
||||
|
||||
fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
|
||||
if (this.isNullOrBlank()) return ""
|
||||
val size = this.length
|
||||
val sizeDifference = size - allowedSize
|
||||
val endIndex = this.indexOf(" ")
|
||||
val startIndex = endIndex - sizeDifference
|
||||
val newString = this.removeRange(startIndex, endIndex)
|
||||
return newString.substring(0 until startIndex) + "..." +
|
||||
newString.substring(startIndex until newString.length)
|
||||
}
|
||||
|
||||
fun String.colorSegment(context: Context, color: Int, startIndex: Int = 0, endIndex: Int = this.length): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
import java.util.Hashtable
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun String.toQrCode(): Bitmap {
|
||||
|
|
@ -58,8 +29,6 @@ fun String.toQrCode(): Bitmap {
|
|||
return bmp
|
||||
}
|
||||
|
||||
fun String.urlEncode(): String = Uri.encode(this)
|
||||
|
||||
fun String.removePrefixOrNull(prefix: String): String? = when {
|
||||
startsWith(prefix) -> substring(prefix.length)
|
||||
else -> null
|
||||
|
|
|
|||
|
|
@ -1,24 +1,3 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean): StringBuilder {
|
||||
if (predicate(value)) this.append(value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun StringBuilder.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): StringBuilder {
|
||||
if (value == null) return this
|
||||
|
||||
prefix?.let { append(it) }
|
||||
append(value)
|
||||
postfix?.let { append(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun String.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): String {
|
||||
return StringBuilder(this).apply { appendIfNotNull(value, prefix, postfix) }.toString()
|
||||
}
|
||||
|
||||
fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count))
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
fun Context.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
Toast.makeText(this, message, length).show()
|
||||
}
|
||||
|
||||
fun Context.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
Toast.makeText(this, this.getString(messageRes), length).show()
|
||||
}
|
||||
|
||||
fun View.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
context.toast(message, length)
|
||||
}
|
||||
|
||||
fun View.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
context.toast(context.getString(messageRes), length)
|
||||
}
|
||||
|
||||
fun Fragment.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
context?.toast(message, length)
|
||||
}
|
||||
|
||||
fun Fragment.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
context?.let { it.toast(it.getString(messageRes), length) }
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.graphics.luminance
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getColor(isTestnet: Boolean = false): Int {
|
||||
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
|
||||
|
||||
return if (isTestnet) {
|
||||
defaultColor
|
||||
} else {
|
||||
try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
defaultColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
|
||||
isTestnet -> Color.WHITE
|
||||
this.getColor().luminance > 0.5 -> Color.BLACK
|
||||
else -> Color.WHITE
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.transition.Transition
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
inline fun Transition.addListener(
|
||||
crossinline onStart: (animator: Transition) -> Unit = {},
|
||||
crossinline onEnd: (animator: Transition) -> Unit = {},
|
||||
crossinline onCancel: (animator: Transition) -> Unit = {},
|
||||
crossinline onPause: (animator: Transition) -> Unit = {},
|
||||
crossinline onRepeat: (animator: Transition) -> Unit = {},
|
||||
): Transition.TransitionListener {
|
||||
val listener = object : Transition.TransitionListener {
|
||||
override fun onTransitionStart(transition: Transition) = onStart(transition)
|
||||
override fun onTransitionEnd(transition: Transition) = onEnd(transition)
|
||||
override fun onTransitionCancel(transition: Transition) = onCancel(transition)
|
||||
override fun onTransitionPause(transition: Transition) = onPause(transition)
|
||||
override fun onTransitionResume(transition: Transition) = onRepeat(transition)
|
||||
}
|
||||
addListener(listener)
|
||||
return listener
|
||||
}
|
||||
|
|
@ -3,25 +3,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, drawableResId)
|
||||
|
|
@ -72,48 +59,9 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
|
|||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
if (invisible) {
|
||||
if (this.visibility == View.INVISIBLE) return
|
||||
|
||||
invokeBeforeStateChanged?.invoke()
|
||||
this.visibility = View.INVISIBLE
|
||||
} else {
|
||||
this.show(invokeBeforeStateChanged)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Int): Int = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
this.resources.displayMetrics,
|
||||
).toInt()
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
fun MaterialCardView.setMargins(
|
||||
marginLeftDp: Int = 16,
|
||||
marginTopDp: Int = 8,
|
||||
marginRightDp: Int = 16,
|
||||
marginBottomDp: Int = 8,
|
||||
) {
|
||||
val params = this.layoutParams
|
||||
(params as ViewGroup.MarginLayoutParams).setMargins(
|
||||
context.dpToPixels(marginLeftDp),
|
||||
context.dpToPixels(marginTopDp),
|
||||
context.dpToPixels(marginRightDp),
|
||||
context.dpToPixels(marginBottomDp),
|
||||
)
|
||||
this.layoutParams = params
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
|
||||
}
|
||||
|
||||
fun Context.copyToClipboard(value: Any, label: String = "") {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
|
||||
|
||||
|
|
@ -140,37 +88,6 @@ fun Context.shareText(text: String) {
|
|||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
fun Fragment.shareText(text: String) {
|
||||
requireContext().shareText(text)
|
||||
}
|
||||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
fun View.animateVisibility(
|
||||
show: Boolean,
|
||||
durationMillis: Long = SHORT_ANIMATION_DURATION,
|
||||
hiddenVisibility: Int = View.GONE,
|
||||
) {
|
||||
if (show) {
|
||||
if (this.visibility == View.VISIBLE) return
|
||||
this.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.alpha = 0f
|
||||
this.isVisible = true
|
||||
}
|
||||
} else {
|
||||
if (this.visibility == hiddenVisibility) return
|
||||
this.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.visibility = hiddenVisibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val SHORT_ANIMATION_DURATION = 80L
|
||||
}
|
||||
|
|
@ -3,15 +3,9 @@ package com.tangem.tap.common.extensions
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
import androidx.core.view.forEach
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.tangem.tap.common.GlobalLayoutStateHandler
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -20,30 +14,6 @@ fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View {
|
|||
return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot)
|
||||
}
|
||||
|
||||
fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
if (this == null) Timber.e("Can't invoke beginDelayedTransition, because parent is NULL")
|
||||
(this as? ViewGroup)?.beginDelayedTransition(transition)
|
||||
}
|
||||
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
||||
fun View.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
(this as? ViewGroup)?.beginDelayedTransition(transition)
|
||||
}
|
||||
|
||||
fun ChipGroup.fitChipsByGroupWidth() {
|
||||
val layoutStateHandler = GlobalLayoutStateHandler(this)
|
||||
layoutStateHandler.onStateChanged = stateHandler@{
|
||||
if (it.childCount < 2) {
|
||||
layoutStateHandler.detach()
|
||||
return@stateHandler
|
||||
}
|
||||
|
||||
val spacingBetweenViews = it.chipSpacingHorizontal * (it.childCount - 1)
|
||||
val width = (it.width - spacingBetweenViews) / it.childCount
|
||||
it.forEach { chip -> (chip as? Chip)?.width = width }
|
||||
layoutStateHandler.detach()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,15 +3,14 @@ package com.tangem.tap.common.extensions
|
|||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -59,7 +58,7 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<Wallet> = try
|
|||
}
|
||||
}
|
||||
|
||||
fun WalletManager.getTopUpUrl(): String? {
|
||||
internal fun WalletManager.getTopUpUrl(): String? {
|
||||
val globalState = store.state.globalState
|
||||
val defaultAddress = wallet.address
|
||||
|
||||
|
|
@ -73,9 +72,28 @@ fun WalletManager.getTopUpUrl(): String? {
|
|||
)
|
||||
}
|
||||
|
||||
fun WalletManager?.getAddressData(): WalletDataModel.AddressData? {
|
||||
internal fun WalletManager?.getAddressData(): WalletAddressData? {
|
||||
val wallet = this?.wallet ?: return null
|
||||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||
}
|
||||
|
||||
private fun Wallet.createAddressesData(): List<WalletAddressData> {
|
||||
val listOfAddressData = mutableListOf<WalletAddressData>()
|
||||
// put a defaultAddress at the first place
|
||||
addresses.forEach {
|
||||
val addressData = WalletAddressData(
|
||||
it.value,
|
||||
it.type,
|
||||
getShareUri(it.value),
|
||||
getExploreUrl(it.value),
|
||||
)
|
||||
if (it.type == AddressType.Default) {
|
||||
listOfAddressData.add(0, addressData)
|
||||
} else {
|
||||
listOfAddressData.add(addressData)
|
||||
}
|
||||
}
|
||||
return listOfAddressData
|
||||
}
|
||||
|
|
@ -2,17 +2,6 @@ package com.tangem.tap.common.extensions
|
|||
|
||||
import android.webkit.WebView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun WebView.configureSettings() {
|
||||
resumeTimers()
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun WebView.stop() {
|
||||
stopLoading()
|
||||
pauseTimers()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.common.feedback
|
|||
import android.os.Build
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.crypto.NetworkType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
|
|
@ -35,6 +36,7 @@ class AdditionalFeedbackInfo {
|
|||
var cardIssuer: String = ""
|
||||
var cardBlockchain: String = ""
|
||||
var userWalletId: String = ""
|
||||
var extendedPublicKey: String = ""
|
||||
|
||||
// wallets
|
||||
val walletsInfo = CopyOnWriteArrayList<EmailWalletInfo>()
|
||||
|
|
@ -70,6 +72,9 @@ class AdditionalFeedbackInfo {
|
|||
cardIssuer = data.card.issuer.name
|
||||
signedHashesCount = formatSignedHashes(data.card.wallets)
|
||||
userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
|
||||
extendedPublicKey = data.card.wallets.firstOrNull { it.extendedPublicKey != null }
|
||||
?.extendedPublicKey
|
||||
?.serialize(networkType = NetworkType.Mainnet).orEmpty()
|
||||
}
|
||||
|
||||
@Deprecated("Don't use it directly")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.breakLine
|
||||
|
||||
class FeedbackDataBuilder(
|
||||
|
|
@ -27,26 +28,32 @@ class FeedbackDataBuilder(
|
|||
}
|
||||
|
||||
fun appendWalletsInfo(): FeedbackDataBuilder {
|
||||
infoHolder.walletsInfo.forEach {
|
||||
infoHolder.walletsInfo.forEach { walletInfo ->
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", it.derivationPath)
|
||||
builder.appendKeyValue("Outputs count", it.outputsCount)
|
||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
||||
|
||||
if (it.tokens.isNotEmpty()) {
|
||||
if (walletInfo.blockchain == Blockchain.Bitcoin) {
|
||||
builder.appendKeyValue("XPUB", infoHolder.extendedPublicKey)
|
||||
}
|
||||
|
||||
builder.appendKeyValue("Outputs count", walletInfo.outputsCount)
|
||||
|
||||
if (walletInfo.tokens.isNotEmpty()) {
|
||||
builder.append("Tokens:")
|
||||
breakLine()
|
||||
it.tokens.forEach { token ->
|
||||
walletInfo.tokens.forEach { token ->
|
||||
builder.appendKeyValue("ID", token.id ?: "[custom token]")
|
||||
builder.appendKeyValue("Name", token.name)
|
||||
builder.appendKeyValue("Contract address", token.contractAddress)
|
||||
}
|
||||
}
|
||||
|
||||
builder.appendKeyValue("Host", it.host)
|
||||
builder.appendKeyValue("Wallet address", it.addresses)
|
||||
builder.appendKeyValue("Explorer link", it.explorerLink)
|
||||
builder.appendKeyValue("Host", walletInfo.host)
|
||||
builder.appendKeyValue("Wallet address", walletInfo.addresses)
|
||||
builder.appendKeyValue("Explorer link", walletInfo.explorerLink)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
package com.tangem.tap.common.qrCodeScan
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutQrScanningBinding
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanQrCodeActivity : AppCompatActivity() {
|
||||
|
||||
private val binding: LayoutQrScanningBinding by viewBinding(LayoutQrScanningBinding::bind)
|
||||
|
||||
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
|
||||
private var cameraExecutor: ExecutorService? = null
|
||||
|
||||
private val binder = PreviewBinder()
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
|
||||
if (!permissionIsGranted()) requestPermission()
|
||||
|
||||
setContentView(R.layout.layout_qr_scanning)
|
||||
|
||||
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
cameraProviderFuture?.addListener(
|
||||
{
|
||||
val cameraProvider = cameraProviderFuture?.get()
|
||||
binder.bindPreview(
|
||||
context = this,
|
||||
binding = binding,
|
||||
lifecycleOwner = this,
|
||||
cameraProvider = requireNotNull(cameraProvider),
|
||||
cameraExecutor = requireNotNull(cameraExecutor),
|
||||
onScanned = { result ->
|
||||
setResult(SCAN_QR_REQUEST_CODE, Intent().apply { putExtra(SCAN_RESULT, result) })
|
||||
finish()
|
||||
},
|
||||
)
|
||||
},
|
||||
ContextCompat.getMainExecutor(this),
|
||||
)
|
||||
|
||||
binding.overlay.post {
|
||||
binding.overlay.setViewFinder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode != PERMISSION_REQUEST_CODE) return
|
||||
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun permissionIsGranted(): Boolean {
|
||||
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
return cameraPermission == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun requestPermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_QR_REQUEST_CODE = 1001
|
||||
const val SCAN_RESULT = "scanResult"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.common.qrCodeScan
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class ViewFinderOverlay(context: Context, attrs: AttributeSet) : View(context, attrs) {
|
||||
|
||||
private val boxPaint: Paint = Paint().apply {
|
||||
color = ContextCompat.getColor(context, R.color.white)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = context.resources.getDimensionPixelOffset(R.dimen.qr_border_stroke_width).toFloat()
|
||||
}
|
||||
|
||||
private val boxWidthRatio = 0.8F
|
||||
private val boxCornerRadius: Float =
|
||||
context.resources.getDimensionPixelOffset(R.dimen.qr_border_corner_radius).toFloat()
|
||||
|
||||
private var boxRect: RectF? = null
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun setViewFinder() {
|
||||
val overlayWidth = width.toFloat()
|
||||
val overlayHeight = height.toFloat()
|
||||
val boxSize = overlayWidth * boxWidthRatio
|
||||
val cx = overlayWidth / 2
|
||||
val cy = overlayHeight / 2
|
||||
boxRect = RectF(cx - boxSize / 2, cy - boxSize / 2, cx + boxSize / 2, cy + boxSize / 2)
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
boxRect?.let {
|
||||
canvas.drawRoundRect(it, boxCornerRadius, boxCornerRadius, boxPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,14 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class AppDialog : StateDialog {
|
||||
data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkErrorDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkWarningDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkDialogRes(
|
||||
val headerId: Int,
|
||||
val messageId: Int,
|
||||
|
|
@ -20,24 +18,33 @@ sealed class AppDialog : StateDialog {
|
|||
val onOk: VoidCallback? = null,
|
||||
) : AppDialog()
|
||||
|
||||
data class OkCancelDialogRes(
|
||||
val headerId: Int,
|
||||
val messageId: Int,
|
||||
val okButton: DialogButton,
|
||||
val cancelButton: DialogButton,
|
||||
) : AppDialog()
|
||||
|
||||
data class DialogButton(
|
||||
val title: Int,
|
||||
val action: VoidCallback? = null,
|
||||
)
|
||||
|
||||
data class AddressInfoDialog(
|
||||
internal data class AddressInfoDialog(
|
||||
val currency: Currency,
|
||||
val addressData: WalletDataModel.AddressData,
|
||||
val addressData: WalletAddressData,
|
||||
) : AppDialog()
|
||||
|
||||
data class TestActionsDialog(
|
||||
val actionsList: List<TestAction>,
|
||||
) : AppDialog()
|
||||
|
||||
data class RussianCardholdersWarningDialog(val data: Data?) : AppDialog() {
|
||||
data class Data(val topUpUrl: String)
|
||||
}
|
||||
|
||||
data class RemoveWalletDialog(
|
||||
val currencyTitle: String,
|
||||
val onOk: () -> Unit,
|
||||
) : AppDialog() {
|
||||
val messageRes: Int = R.string.token_details_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_hide_alert_title
|
||||
val primaryButtonRes: Int = R.string.token_details_hide_alert_hide
|
||||
}
|
||||
|
||||
data class TokensAreLinkedDialog(
|
||||
val currencyTitle: String,
|
||||
val currencySymbol: String,
|
||||
) : AppDialog() {
|
||||
val messageRes: Int = R.string.token_details_unable_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_unable_hide_alert_title
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,6 @@ import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
|||
import com.tangem.tap.features.shop.redux.ShopReducer
|
||||
import com.tangem.tap.features.signin.redux.SignInReducer
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
|
|
@ -33,7 +31,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
|
||||
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
|
||||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
walletState = WalletReducer.reduce(action, state, appStateHolder),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
sendState = SendScreenReducer.reduce(action, state.sendState),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
|
|
@ -43,7 +40,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
shopState = ShopReducer.reduce(action, state.shopState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
||||
walletSelectorState = WalletSelectorReducer.reduce(action, state),
|
||||
signInState = SignInReducer.reduce(action, state),
|
||||
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,10 +35,7 @@ import com.tangem.tap.features.signin.redux.SignInMiddleware
|
|||
import com.tangem.tap.features.signin.redux.SignInState
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
|
||||
|
|
@ -54,7 +51,6 @@ data class AppState(
|
|||
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
|
||||
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
|
||||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val walletState: WalletState = WalletState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val sendState: SendState = SendState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
|
|
@ -64,7 +60,6 @@ data class AppState(
|
|||
val shopState: ShopState = ShopState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
|
||||
val signInState: SignInState = SignInState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
|
@ -72,7 +67,7 @@ data class AppState(
|
|||
private val domainState: DomainState
|
||||
get() = domainStore.state
|
||||
|
||||
val domainNetworks: NetworkServices
|
||||
private val domainNetworks: NetworkServices
|
||||
get() = domainState.globalState.networkServices
|
||||
|
||||
val featureRepositoryProvider: FeatureRepositoryProvider
|
||||
|
|
@ -92,7 +87,6 @@ data class AppState(
|
|||
OnboardingNoteMiddleware.handler,
|
||||
OnboardingWalletMiddleware.handler,
|
||||
OnboardingOtherCardsMiddleware.handler,
|
||||
WalletMiddleware().walletMiddleware,
|
||||
TwinCardsMiddleware.handler,
|
||||
SendMiddleware().sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
|
|
@ -103,12 +97,12 @@ data class AppState(
|
|||
ShopMiddleware().shopMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
SaveWalletMiddleware().middleware,
|
||||
WalletSelectorMiddleware().middleware,
|
||||
LockUserWalletsTimerMiddleware().middleware,
|
||||
AccessCodeRequestPolicyMiddleware().middleware,
|
||||
SignInMiddleware.middleware,
|
||||
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||
LegacyMiddleware.legacyMiddleware,
|
||||
TradeCryptoMiddleware.middleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.tap.features.home.data.HomeRepositoryImpl
|
||||
import com.tangem.tap.features.home.domain.HomeRepository
|
||||
import com.tangem.tap.features.wallet.data.WalletRepositoryImpl
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) {
|
||||
|
||||
val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers)
|
||||
|
||||
val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers)
|
||||
}
|
||||
|
|
@ -20,22 +20,14 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
|
|||
private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
|
||||
private var baseLayout = basicCoordinatorLayout
|
||||
|
||||
fun replaceBaseLayout(coordinatorLayout: CoordinatorLayout) {
|
||||
baseLayout = WeakReference(coordinatorLayout)
|
||||
}
|
||||
|
||||
fun returnBaseLayout() {
|
||||
baseLayout = basicCoordinatorLayout
|
||||
}
|
||||
|
||||
fun showNotification(message: String) {
|
||||
private fun showNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar -> snackbar.show() }
|
||||
}
|
||||
}
|
||||
|
||||
fun showDebugNotification(message: String) {
|
||||
private fun showDebugNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar ->
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
abstract class Request : Action {
|
||||
abstract suspend fun execute()
|
||||
}
|
||||
|
|
@ -5,17 +5,15 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.common.analytics.topup.TopUpController
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.feedback.FeedbackData
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
|
|
@ -26,7 +24,6 @@ sealed class GlobalAction : Action {
|
|||
|
||||
// notifications
|
||||
data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction
|
||||
data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction
|
||||
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
|
||||
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
|
||||
|
||||
|
|
@ -61,9 +58,9 @@ sealed class GlobalAction : Action {
|
|||
|
||||
data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
|
||||
|
||||
data class ChangeAppCurrency(val appCurrency: FiatCurrency) : GlobalAction()
|
||||
data class ChangeAppCurrency(val appCurrency: AppCurrency) : GlobalAction()
|
||||
object RestoreAppCurrency : GlobalAction() {
|
||||
data class Success(val appCurrency: FiatCurrency) : GlobalAction()
|
||||
data class Success(val appCurrency: AppCurrency) : GlobalAction()
|
||||
}
|
||||
|
||||
data class UpdateWalletSignedHashes(
|
||||
|
|
@ -78,7 +75,6 @@ sealed class GlobalAction : Action {
|
|||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
|
||||
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
|
||||
data class SetTopUpController(val topUpController: TopUpController) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
|
||||
data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction()
|
||||
|
|
|
|||
|
|
@ -6,19 +6,18 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.network.exchangeServices.BuyExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.CardExchangeRules
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
@ -27,6 +26,9 @@ import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
|
|||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -67,17 +69,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
val daggerGraphState = store.state.daggerGraphState
|
||||
val walletFeatureToggles = daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
val detailsFeatureToggles = daggerGraphState.get(DaggerGraphState::detailsFeatureToggles)
|
||||
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled ||
|
||||
detailsFeatureToggles.isRedesignedAppCurrencySelectorEnabled
|
||||
) {
|
||||
restoreAppCurrencyNew()
|
||||
} else {
|
||||
restoreAppCurrencyLegacy()
|
||||
}
|
||||
restoreAppCurrency()
|
||||
}
|
||||
is GlobalAction.HideWarningMessage -> {
|
||||
store.state.globalState.warningManager?.let {
|
||||
|
|
@ -87,7 +79,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
// store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
// }
|
||||
|
||||
store.dispatch(WalletAction.Warnings.Update)
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
}
|
||||
}
|
||||
|
|
@ -115,9 +106,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
feedbackManager.openChat(chatConfig, action.feedbackData)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
|
||||
}
|
||||
is GlobalAction.UpdateFeedbackInfo -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
|
|
@ -169,9 +157,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.SetTopUpController -> {
|
||||
walletCurrenciesManager.addListener(action.topUpController)
|
||||
}
|
||||
is GlobalAction.UpdateUserWalletsListManager -> {
|
||||
val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
|
||||
|
||||
|
|
@ -201,23 +186,12 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun restoreAppCurrencyLegacy() {
|
||||
store.dispatch(
|
||||
GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
|
||||
?.run { FiatCurrency(code, name, symbol) }
|
||||
?: FiatCurrency.Default,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun restoreAppCurrencyNew() {
|
||||
private fun restoreAppCurrency() {
|
||||
scope.launch {
|
||||
val currency = store.state.daggerGraphState.get(DaggerGraphState::appCurrencyRepository)
|
||||
.getSelectedAppCurrency()
|
||||
.firstOrNull()
|
||||
?.run { FiatCurrency(code, name, symbol) }
|
||||
?: FiatCurrency.Default
|
||||
?: AppCurrency.Default
|
||||
|
||||
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
|
||||
is GlobalAction.SetTopUpController -> globalState.copy(topUpController = action.topUpController)
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanResponse?.card ?: return globalState
|
||||
val wallet = card.wallets
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
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.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
|
|
@ -23,13 +22,12 @@ data class GlobalState(
|
|||
val configManager: ConfigManager? = null,
|
||||
val warningManager: WarningMessagesManager? = null,
|
||||
val feedbackManager: FeedbackManager? = null,
|
||||
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val appCurrency: AppCurrency = AppCurrency.Default,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
val userCountryCode: String? = null,
|
||||
val userWalletsListManager: UserWalletsListManager? = null,
|
||||
val topUpController: TopUpController? = null,
|
||||
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
return Result.success(products)
|
||||
}
|
||||
|
||||
suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
|
||||
private suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
|
||||
val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout"))
|
||||
|
||||
val result = if (promoCode.isBlank()) {
|
||||
|
|
|
|||
|
|
@ -19,26 +19,7 @@ import kotlin.coroutines.suspendCoroutine
|
|||
|
||||
@Suppress("LargeClass")
|
||||
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
|
||||
val client: GraphClient by lazy { initClient() }
|
||||
|
||||
suspend fun getShopName(): Result<String> {
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.shop { shopQuery: ShopQuery ->
|
||||
shopQuery
|
||||
.name()
|
||||
}
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
val name = result.response.data!!.shop.name
|
||||
Result.success(name)
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
private val client: GraphClient by lazy { initClient() }
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.ContentViewCallback
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -27,15 +31,29 @@ class MaxAmountSnackbar(
|
|||
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
|
||||
"No suitable parent found from the given view. Please provide a valid view.",
|
||||
)
|
||||
val inflater = LayoutInflater.from(view.context)
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
|
||||
customView.setOnClickListener { onClick() }
|
||||
|
||||
return MaxAmountSnackbar(parent, customView).apply {
|
||||
updateBottomMargin()
|
||||
duration = Snackbar.LENGTH_INDEFINITE
|
||||
}
|
||||
}
|
||||
|
||||
private fun MaxAmountSnackbar.updateBottomMargin() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this.view) { _, insets ->
|
||||
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||
val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
||||
|
||||
this.view.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
bottomMargin = imeInsets - bottomInsets + context.dpToPx(dp = 8f).toInt()
|
||||
}
|
||||
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
private fun View?.findSuitableParent(): ViewGroup? {
|
||||
var view = this
|
||||
var fallback: ViewGroup? = null
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ abstract class BaseTruncate : Truncate {
|
|||
protected var hasBeenTruncated = false
|
||||
|
||||
override fun apply(tv: TextView, text: String, with: String): String {
|
||||
val roughLength = getRoughFitLength(tv, text, with)
|
||||
val roughLength = getRoughFitLength(tv, text)
|
||||
val fittedText = preciseFitting(tv, roughTruncate(text, roughLength), with)
|
||||
return if (hasBeenTruncated) attachWith(fittedText, with) else fittedText
|
||||
}
|
||||
|
||||
protected fun getRoughFitLength(tv: TextView, text: String, with: String): Int {
|
||||
private fun getRoughFitLength(tv: TextView, text: String): Int {
|
||||
val existingSpace = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
|
||||
val textWillTakeSpace = tv.paint.measureText(text)
|
||||
val overSizeRatio: Float = textWillTakeSpace / existingSpace
|
||||
|
|
@ -44,7 +44,7 @@ abstract class BaseTruncate : Truncate {
|
|||
return maxLengthOfText.toInt()
|
||||
}
|
||||
|
||||
protected fun preciseFitting(tv: TextView, text: String, with: String): String {
|
||||
private fun preciseFitting(tv: TextView, text: String, with: String): String {
|
||||
if (!hasBeenTruncated) return text
|
||||
|
||||
val spaceForText = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
|
||||
|
|
@ -126,11 +126,5 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
|
|||
return truncate.apply(this, text, with)
|
||||
}
|
||||
|
||||
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.START, with)
|
||||
|
||||
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
|
||||
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.END, with)
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
|
|
@ -3,9 +3,9 @@ package com.tangem.tap.common.toggleWidget
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -2,25 +2,16 @@ package com.tangem.tap.common.toggleWidget
|
|||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationSet
|
||||
import android.view.animation.AnticipateOvershootInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.view.animation.RotateAnimation
|
||||
import android.view.animation.ScaleAnimation
|
||||
import android.view.animation.*
|
||||
import android.widget.ViewSwitcher
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class RefreshBalanceWidget(
|
||||
private val root: ViewGroup,
|
||||
) : ViewStateWidget {
|
||||
class RefreshBalanceWidget(root: ViewGroup) : ViewStateWidget {
|
||||
|
||||
var isShowing: Boolean? = null
|
||||
private set
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
|
|
@ -7,14 +7,9 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchShare
|
||||
import com.tangem.tap.common.extensions.dispatchToastNotification
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
||||
|
|
@ -22,7 +17,7 @@ import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressInfoBottomSheetDialog(
|
||||
internal class AddressInfoBottomSheetDialog(
|
||||
private val stateDialog: AppDialog.AddressInfoDialog,
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
|
@ -47,7 +42,7 @@ class AddressInfoBottomSheetDialog(
|
|||
showData(data = stateDialog.addressData)
|
||||
}
|
||||
|
||||
private fun showData(data: WalletDataModel.AddressData) = with(binding!!) {
|
||||
private fun showData(data: WalletAddressData) = with(binding!!) {
|
||||
pseudoToolbar.imvClose.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
|
|
@ -57,7 +52,6 @@ class AddressInfoBottomSheetDialog(
|
|||
btnFlCopyAddress.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
context.copyToClipboard(data.address)
|
||||
store.dispatchToastNotification(R.string.copy_toast_msg)
|
||||
}
|
||||
btnFlShare.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
|
|
@ -6,17 +6,16 @@ import android.view.LayoutInflater
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
|
||||
|
||||
class RussianCardholdersWarningBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data?,
|
||||
private val dialogData: AppDialog.RussianCardholdersWarningDialog.Data?,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
private var binding: DialogRussiansCardholdersWarningBinding? = null
|
||||
|
|
@ -40,9 +39,8 @@ class RussianCardholdersWarningBottomSheetDialog(
|
|||
if (dialogData != null) {
|
||||
store.dispatchOpenUrl(dialogData.topUpUrl)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
} else {
|
||||
store.dispatch(TradeCryptoAction.Buy(checkUserLocation = false))
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
binding?.btnNo?.setOnClickListener {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
31
app/src/main/java/com/tangem/tap/common/ui/SimpleOkDialog.kt
Normal file
31
app/src/main/java/com/tangem/tap/common/ui/SimpleOkDialog.kt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object SimpleOkDialog {
|
||||
|
||||
fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog {
|
||||
val message = if (dialog.args.isEmpty()) {
|
||||
context.getString(dialog.messageId)
|
||||
} else {
|
||||
context.getString(dialog.messageId, *dialog.args.toTypedArray())
|
||||
}
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(dialog.headerId))
|
||||
setMessage(message)
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> }
|
||||
setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
dialog.onOk?.invoke()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.common.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
private val mainLooper by lazy { Looper.getMainLooper() }
|
||||
private val mainHandler by lazy { Handler(mainLooper) }
|
||||
|
||||
/**
|
||||
* A subscriber interface for safely subscribing to state changes in a Store.
|
||||
*
|
||||
* @param State the type of the state in the Store
|
||||
*/
|
||||
interface SafeStoreSubscriber<State> : StoreSubscriber<State> {
|
||||
|
||||
/**
|
||||
* A function that will be called when the state in the Store changes.
|
||||
*
|
||||
* @param state the new state in the Store
|
||||
*/
|
||||
override fun newState(state: State) {
|
||||
if (Thread.currentThread() != mainLooper.thread) {
|
||||
mainHandler.post { newStateOnMain(state) }
|
||||
} else {
|
||||
newStateOnMain(state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that will be called on the main thread when the state in the Store changes.
|
||||
*
|
||||
* @param state the new state in the Store
|
||||
*/
|
||||
fun newStateOnMain(state: State)
|
||||
}
|
||||
|
|
@ -21,4 +21,8 @@ internal class RuntimeUserWalletsStore(
|
|||
?.firstOrNull()
|
||||
?.singleOrNull { it.walletId == key }
|
||||
}
|
||||
|
||||
override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) {
|
||||
walletsStateHolder.userWalletsListManager?.update(userWalletId, update)
|
||||
}
|
||||
}
|
||||
27
app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt
Normal file
27
app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.card.DefaultDerivationsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardDataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesDerivationsRepository(
|
||||
tangemSdkManager: TangemSdkManager,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DerivationsRepository {
|
||||
return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, dispatchers)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.card.*
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
|
|
@ -61,8 +62,14 @@ internal object CardDomainModule {
|
|||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideDerivePublicKeysUseCase(tangemSdkManager: TangemSdkManager): DerivePublicKeysUseCase {
|
||||
return DefaultDerivePublicKeysUseCase(tangemSdkManager = tangemSdkManager)
|
||||
fun provideDerivePublicKeysUseCase(
|
||||
tangemSdkManager: TangemSdkManager,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
): DerivePublicKeysUseCase {
|
||||
return DefaultDerivePublicKeysUseCase(
|
||||
tangemSdkManager = tangemSdkManager,
|
||||
derivationsRepository = derivationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository
|
||||
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object QrScanningDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideListenToQrScanUseCase(repository: QrScanningEventsRepository): ListenToQrScanningUseCase {
|
||||
return ListenToQrScanningUseCase(repository)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,18 @@ import dagger.hilt.android.scopes.ViewModelScoped
|
|||
@Suppress("TooManyFunctions")
|
||||
internal object TokensDomainModule {
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideAddCryptoCurrenciesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideFetchTokenListUseCase(
|
||||
|
|
@ -203,6 +215,22 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetFeePaidCryptoCurrencyStatusSyncUseCase {
|
||||
return GetFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
|
|
@ -242,4 +270,44 @@ internal object TokensDomainModule {
|
|||
fun provideGetGlobalTokenListUseCase(tokensListRepository: TokensListRepository): GetGlobalTokenListUseCase {
|
||||
return GetGlobalTokenListUseCase(repository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideCheckTokenCompatibilityUseCase(
|
||||
repository: NetworksCompatibilityRepository,
|
||||
): CheckCurrencyCompatibilityUseCase {
|
||||
return CheckCurrencyCompatibilityUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideFindTokenByContractAddressUseCase(
|
||||
tokensListRepository: TokensListRepository,
|
||||
): FindTokenByContractAddressUseCase {
|
||||
return FindTokenByContractAddressUseCase(repository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideValidateContractAddressUseCase(
|
||||
tokensListRepository: TokensListRepository,
|
||||
): ValidateContractAddressUseCase {
|
||||
return ValidateContractAddressUseCase(tokensListRepository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideAreTokensSupportedByNetworkUseCase(
|
||||
repository: NetworksCompatibilityRepository,
|
||||
): AreTokensSupportedByNetworkUseCase {
|
||||
return AreTokensSupportedByNetworkUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetNetworksSupportedByWallet(
|
||||
repository: NetworksCompatibilityRepository,
|
||||
): GetNetworksSupportedByWallet {
|
||||
return GetNetworksSupportedByWallet(repository = repository)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -38,4 +40,10 @@ internal object TransactionDomainModule {
|
|||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
|
||||
return CreateTransactionUseCase(transactionRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,10 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -91,4 +93,36 @@ internal object WalletsDomainModule {
|
|||
fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase {
|
||||
return ShouldSaveUserWalletsUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ValidateWalletAddressUseCase {
|
||||
return ValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository = walletAddressServiceRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesValidateWalletMemoUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
): ValidateWalletMemoUseCase {
|
||||
return ValidateWalletMemoUseCase(walletAddressServiceRepository = walletAddressServiceRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesParseSharedAddressUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ParseSharedAddressUseCase {
|
||||
return ParseSharedAddressUseCase(
|
||||
walletAddressServiceRepository = walletAddressServiceRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,8 @@ import com.tangem.operations.derivation.DerivationTaskResponse
|
|||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.derivationsFinder
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
|
|
@ -126,15 +125,6 @@ class TangemSdkManager(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun createWallet(cardId: String?): CompletionResult<CardDTO> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
CreateWalletAndRescanTask(),
|
||||
cardId,
|
||||
initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
.map { CardDTO(it) }
|
||||
}
|
||||
|
||||
suspend fun derivePublicKeys(
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
|
|
@ -267,6 +257,9 @@ class TangemSdkManager(
|
|||
filter = CardFilter(
|
||||
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
|
||||
maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33),
|
||||
batchIdFilter = CardFilter.Companion.ItemFilter.Deny(
|
||||
items = setOf("0027", "0030", "0031", "0035", "DA88"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ sealed class TapError(
|
|||
val stateError: String,
|
||||
) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError"))
|
||||
|
||||
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
|
||||
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
|
||||
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
|
||||
data class AmountLowerExistentialDeposit(
|
||||
|
|
@ -41,7 +40,6 @@ sealed class TapError(
|
|||
object DustChange : TapError(R.string.send_error_dust_change)
|
||||
|
||||
sealed class WalletManager {
|
||||
object CreationError : CustomError(customMessage = "Can't create wallet manager")
|
||||
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
|
||||
class InternalError(message: String) : CustomError(message)
|
||||
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
|
||||
|
|
|
|||
|
|
@ -4,39 +4,27 @@ 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
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class TapWalletManager(
|
||||
private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(),
|
||||
|
|
@ -55,15 +43,15 @@ class TapWalletManager(
|
|||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
|
||||
// If a previous job was running, it gets cancelled before the new one starts,
|
||||
// ensuring that only one job is active at any given time.
|
||||
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
||||
.launch { loadUserWalletData(userWallet, refresh, sendAnalyticsEvent) }
|
||||
.launch { loadUserWalletData(userWallet, sendAnalyticsEvent) }
|
||||
.also { it.join() }
|
||||
}
|
||||
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
|
||||
Analytics.setContext(userWallet.scanResponse)
|
||||
if (sendAnalyticsEvent) {
|
||||
Analytics.send(Basic.WalletOpened())
|
||||
|
|
@ -78,114 +66,11 @@ class TapWalletManager(
|
|||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
|
||||
store.dispatchWalletAction(action = WalletAction.UserWalletChanged(userWallet))
|
||||
store.dispatchWalletAction(
|
||||
action = WalletAction.UpdateCanSaveUserWallets(
|
||||
canSaveUserWallets = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.shouldSaveUserWalletsSync(),
|
||||
),
|
||||
)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(WalletConnectAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
|
||||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
store.dispatchWalletAction(action = WalletAction.Warnings.CheckIfNeeded)
|
||||
}
|
||||
|
||||
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
setupWalletConnectV2(userWallet)
|
||||
loadData(userWallet = userWallet, refresh = refresh)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Store<AppState>.dispatchWalletAction(action: WalletAction) {
|
||||
val walletFeatureToggles = state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
dispatch(action = action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWalletConnectV2(userWallet: UserWallet) {
|
||||
val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
|
||||
userWallet.cardId
|
||||
} else { // if wallet has backup, any card from wallet can be used to sign
|
||||
null
|
||||
}
|
||||
scope.launch {
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
|
||||
wcInteractor.startListening(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) {
|
||||
walletStoresManager.fetch(userWallet, refresh)
|
||||
.doOnSuccess {
|
||||
Timber.d("Wallet stores fetched for ${userWallet.walletId}")
|
||||
store.dispatchOnMain(WalletAction.LoadData.Success)
|
||||
store.state.globalState.topUpController?.loadDataSuccess()
|
||||
store.dispatchWithMain(WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded)
|
||||
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor
|
||||
wcInteractor?.setUserChains(getAccountsForWc(wcInteractor))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
val errorAction = when (error) {
|
||||
is WalletStoresError -> when (error) {
|
||||
is WalletStoresError.FetchFiatRatesError -> WalletAction.LoadData.Failure(error = null)
|
||||
is WalletStoresError.UpdateWalletManagerTokensError -> WalletAction.LoadData.Failure(
|
||||
error = TapError.WalletManager.InternalError(
|
||||
message = error.cause.localizedMessage ?: error.customMessage,
|
||||
),
|
||||
)
|
||||
is WalletStoresError.WalletManagerNotCreated -> WalletAction.LoadData.Failure(
|
||||
error = TapError.WalletManager.CreationError,
|
||||
)
|
||||
is WalletStoresError.UnknownBlockchain -> WalletAction.LoadData.Failure(
|
||||
error = TapError.UnknownBlockchain,
|
||||
)
|
||||
is WalletStoresError.NoInternetConnection -> WalletAction.LoadData.Failure(
|
||||
error = TapError.NoInternetConnection,
|
||||
)
|
||||
}
|
||||
else -> WalletAction.LoadData.Failure(error = null)
|
||||
}
|
||||
|
||||
Timber.e(error, "Wallet stores fetching failed for ${userWallet.walletId}")
|
||||
|
||||
store.dispatchOnMain(errorAction)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
val walletManagers = if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
|
||||
return walletManagers.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import timber.log.Timber
|
||||
|
||||
internal typealias Derivations = Map<ByteArrayKey, List<DerivationPath>>
|
||||
private typealias DerivedKeys = Map<ByteArrayKey, ExtendedPublicKeysMap>
|
||||
|
||||
internal class DefaultDerivationsRepository(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : DerivationsRepository {
|
||||
|
||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
|
||||
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
|
||||
Timber.d("Nothing to derive")
|
||||
return
|
||||
}
|
||||
|
||||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||
.find(currencies)
|
||||
.ifEmpty {
|
||||
Timber.d("Nothing to derive")
|
||||
return
|
||||
}
|
||||
|
||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||
.doOnSuccess { response ->
|
||||
updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold(
|
||||
onSuccess = { return },
|
||||
onFailure = { throw it },
|
||||
)
|
||||
}
|
||||
.doOnFailure { throw it }
|
||||
|
||||
error("This code should never be reached")
|
||||
}
|
||||
|
||||
private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result<Unit> {
|
||||
return runCatching(dispatchers.io) {
|
||||
userWalletsStore.update(
|
||||
userWalletId = userWalletId,
|
||||
update = { userWallet -> userWallet.updateDerivedKeys(keys) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.updateDerivedKeys(keys: DerivedKeys): UserWallet {
|
||||
return copy(
|
||||
scanResponse = scanResponse.copy(
|
||||
derivedKeys = getUpdatedDerivedKeys(oldKeys = scanResponse.derivedKeys, newKeys = keys),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getUpdatedDerivedKeys(oldKeys: DerivedKeys, newKeys: DerivedKeys): DerivedKeys {
|
||||
return (oldKeys.keys + newKeys.keys).toSet()
|
||||
.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,15 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
internal class DefaultDerivePublicKeysUseCase(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
) : DerivePublicKeysUseCase {
|
||||
|
||||
override suspend fun invoke(
|
||||
|
|
@ -26,4 +29,13 @@ internal class DefaultDerivePublicKeysUseCase(
|
|||
|
||||
return Unit.left()
|
||||
}
|
||||
|
||||
override suspend fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> {
|
||||
return Either.catch {
|
||||
derivationsRepository.derivePublicKeys(userWalletId, currencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||
|
||||
/**
|
||||
* Finder of missed derivations
|
||||
*
|
||||
* @property scanResponse scanning response
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||
|
||||
/** Find missed derivations for given currencies [currencies] */
|
||||
fun find(currencies: List<CryptoCurrency>): Derivations {
|
||||
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
currencies
|
||||
.mapToNewDerivations()
|
||||
.forEach { data ->
|
||||
val current = this[data.first]
|
||||
if (current != null) {
|
||||
current.addAll(data.second)
|
||||
current.distinct()
|
||||
} else {
|
||||
this[data.first] = data.second.toMutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrency>.mapToNewDerivations(): List<DerivationData> {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
return mapNotNull { currency ->
|
||||
val blockchain = Blockchain.fromId(id = currency.network.id.value)
|
||||
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
||||
|
||||
findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findNewDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: CryptoCurrency,
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
val publicKey = wallet.publicKey.toMapKey()
|
||||
|
||||
val derivationCandidates = currency
|
||||
.getDerivationCandidates(curve)
|
||||
.ifEmpty { return null }
|
||||
.filterAlreadyDerivedKeys(publicKey)
|
||||
.ifEmpty { return null }
|
||||
|
||||
return publicKey to derivationCandidates
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
||||
|
||||
return buildList {
|
||||
add(blockchain.getDerivationPath(curve = curve))
|
||||
add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates))
|
||||
add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates))
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun Blockchain.getDerivationPath(curve: EllipticCurve): DerivationPath? {
|
||||
return if (getSupportedCurves().contains(curve)) {
|
||||
derivationPath(style = scanResponse.derivationStyleProvider.getDerivationStyle())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? {
|
||||
return if (getSupportedCurves().contains(curve)) {
|
||||
currency.network.derivationPath.value?.let(::DerivationPath)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? {
|
||||
return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) {
|
||||
currency.network.derivationPath.value?.let {
|
||||
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<DerivationPath>.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
||||
val alreadyDerivedPaths = getAlreadyDerivedKeys(publicKey)
|
||||
return filterNot(alreadyDerivedPaths::contains)
|
||||
}
|
||||
|
||||
private fun getAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
||||
val extendedPublicKeysMap = scanResponse.derivedKeys[publicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
return extendedPublicKeysMap.keys.toList()
|
||||
}
|
||||
}
|
||||
|
|
@ -51,14 +51,12 @@ data class WarningMessage(
|
|||
}
|
||||
|
||||
enum class Location {
|
||||
@Json(name = "main")
|
||||
MainScreen,
|
||||
|
||||
@Json(name = "send")
|
||||
SendScreen,
|
||||
}
|
||||
|
||||
enum class Origin {
|
||||
Local, Remote
|
||||
Remote,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,17 @@
|
|||
package com.tangem.tap.domain.configurable.warningMessage
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.utils.extensions.removeBy
|
||||
import com.tangem.wallet.R
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
// TODO: Delete with SendFeatureToggles
|
||||
@Deprecated(message = "Used only in old send screen")
|
||||
class WarningMessagesManager {
|
||||
|
||||
private val warningsList = CopyOnWriteArrayList<WarningMessage>()
|
||||
|
||||
fun addWarning(warning: WarningMessage) {
|
||||
if (findWarning(warning) == null) {
|
||||
warningsList.add(warning)
|
||||
sortByPriority()
|
||||
}
|
||||
}
|
||||
|
||||
fun getWarnings(location: WarningMessage.Location, blockchains: List<Blockchain>): List<WarningMessage> {
|
||||
return warningsList.filter { message ->
|
||||
val messageBlockchains = message.blockchainList
|
||||
|
|
@ -45,131 +36,7 @@ class WarningMessagesManager {
|
|||
}
|
||||
}
|
||||
|
||||
fun removeWarnings(origin: WarningMessage.Origin) {
|
||||
warningsList.removeBy { it.origin == origin }
|
||||
sortByPriority()
|
||||
}
|
||||
|
||||
fun removeWarnings(messageRes: Int) {
|
||||
warningsList.removeBy { it.messageResId == messageRes }
|
||||
}
|
||||
|
||||
fun containsWarning(warning: WarningMessage) = warning in warningsList
|
||||
|
||||
private fun sortByPriority() {
|
||||
warningsList.sortBy { it.priority.ordinal }
|
||||
}
|
||||
|
||||
private fun findWarning(warning: WarningMessage): WarningMessage? {
|
||||
return warningsList.firstOrNull { it == warning }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
|
||||
val devCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
// messageResId = R.string.alert_developer_card,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
val alreadySignedHashesWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
// messageResId = R.string.alert_card_signed_transactions,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
val signedHashesMultiWalletWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
// titleResId = R.string.warning_important_security_info,
|
||||
// messageResId = R.string.warning_signed_tx_previously,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
buttonTextId = R.string.warning_button_learn_more,
|
||||
titleFormatArg = "\u26A0",
|
||||
)
|
||||
|
||||
val appRatingWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.AppRating,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.warning_rate_app_title,
|
||||
messageResId = R.string.warning_rate_app_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
val onlineVerificationFailed = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.warning_failed_to_verify_card_title,
|
||||
messageResId = R.string.warning_failed_to_verify_card_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
val testCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.TestCard,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.warning_testnet_card_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
val demoCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
// messageResId = R.string.alert_demo_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage {
|
||||
return WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
// messageResId = R.string.warning_low_signatures_format,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
messageFormatArg = remainingSignatures.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
// fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
|
||||
// return warning.messageResId == R.string.alert_card_signed_transactions
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,17 +9,6 @@ import com.tangem.domain.userwallets.Artwork
|
|||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
|
||||
val CardDTO.remainingSignatures: Int?
|
||||
get() = this.wallets.firstOrNull()?.remainingSignatures
|
||||
|
||||
val CardDTO.isHdWalletAllowedByApp: Boolean
|
||||
get() = settings.isHDWalletAllowed
|
||||
|
||||
@Suppress("UnnecessaryParentheses")
|
||||
fun CardDTO.hasSignedHashes(): Boolean {
|
||||
return wallets.any { (it.totalSignedHashes ?: 0) > 0 }
|
||||
}
|
||||
|
||||
fun CardDTO.signedHashesCount(): Int {
|
||||
return wallets.sumOf { it.totalSignedHashes ?: 0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
private val tokenCustomIconUrls = mutableMapOf<String, String>()
|
||||
|
||||
fun Token.getCustomIconUrl(): String? {
|
||||
return tokenCustomIconUrls[this.contractAddress]
|
||||
}
|
||||
|
||||
fun Token.setCustomIconUrl(url: String) {
|
||||
tokenCustomIconUrls[this.contractAddress] = url
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue