Updated on 2026-08-14
This commit is contained in:
commit
cc3401a412
943 changed files with 15667 additions and 6451 deletions
17
core/analytics/models/detekt-baseline-main.xml
Normal file
17
core/analytics/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:TechAnalyticsEvent.kt$TechAnalyticsEvent.KeyboardIdentifier${ put("Package", it) put("GPUrl", "https://play.google.com/store/apps/details?id=$packageName") }</ID>
|
||||
<ID>UseEmptyCounterpart:AnalyticsEvent.kt$AnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:Basic.kt$Basic$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:ExceptionAnalyticsEvent.kt$ExceptionAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:MainScreenAnalyticsEvent.kt$MainScreenAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.CreateWallet$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.Error$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.Onboarding$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.SeedPhrase$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:TechAnalyticsEvent.kt$TechAnalyticsEvent$mapOf()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -11,11 +11,6 @@ sealed class AnalyticsParam {
|
|||
companion object
|
||||
}
|
||||
|
||||
sealed class TokenBalanceState(val value: String) {
|
||||
data object Empty : TokenBalanceState("Empty")
|
||||
data object Full : TokenBalanceState("Full")
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
data object Liked : RateApp("Liked")
|
||||
data object Disliked : RateApp("Disliked")
|
||||
|
|
@ -83,12 +78,14 @@ sealed class AnalyticsParam {
|
|||
data object Onboarding : ScreensSources("Onboarding")
|
||||
data object LongTap : ScreensSources("Long Tap")
|
||||
data object Markets : ScreensSources("Markets")
|
||||
data object HotWallet : ScreensSources("Hot Wallet")
|
||||
data object TangemPay : ScreensSources("Tangem Pay")
|
||||
data object WalletSettings : ScreensSources("Wallet Settings")
|
||||
data object Upgrade : ScreensSources("Upgrade")
|
||||
data object HardwareWallet : ScreensSources("Hardware Wallet")
|
||||
data object ImportWallet : ScreensSources("Import Wallet")
|
||||
data object CreateWalletIntro : ScreensSources("Create Wallet Intro")
|
||||
data object AddNewWallet : ScreensSources("Add New Wallet")
|
||||
data object CreateWallet : ScreensSources("Create Wallet")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
|
|
@ -203,8 +200,9 @@ sealed class AnalyticsParam {
|
|||
Pending(value = "Pending"),
|
||||
}
|
||||
|
||||
enum class EnsStatus(val value: String) {
|
||||
EMPTY("Empty"), FULL("Full")
|
||||
enum class EmptyFull(val value: String) {
|
||||
Empty("Empty"),
|
||||
Full("Full"),
|
||||
}
|
||||
|
||||
enum class ProductType(val value: String) {
|
||||
|
|
@ -268,5 +266,6 @@ sealed class AnalyticsParam {
|
|||
const val CHOSEN_TOKEN = "Token Chosen"
|
||||
const val ENS = "ENS"
|
||||
const val ENS_ADDRESS = "ENS Address"
|
||||
const val ACCOUNT_DERIVATION_FROM = "Account Derivation (from)"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.core.analytics.models
|
||||
|
||||
/**
|
||||
* Marker interface for AppsFlyer events
|
||||
* Only events implementing this interface will be sent to AppsFlyer
|
||||
*/
|
||||
interface AppsFlyerOnlyEvent
|
||||
|
||||
/**
|
||||
* Marker interface for AppsFlyer included events
|
||||
* Events implementing this interface will be sent to AppsFlyer along with other analytics handlers
|
||||
*/
|
||||
interface AppsFlyerIncludedEvent {
|
||||
val appsFlyerReplacedEvent: String
|
||||
}
|
||||
|
|
@ -10,11 +10,11 @@ sealed class Basic(
|
|||
) : Basic(
|
||||
event = "Card Was Scanned",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
AnalyticsParam.Key.SOURCE to source.value,
|
||||
),
|
||||
)
|
||||
|
||||
class SignedIn(
|
||||
class SignedInLegacy(
|
||||
currency: AnalyticsParam.WalletType,
|
||||
batch: String,
|
||||
signInType: SignInType,
|
||||
|
|
@ -24,8 +24,8 @@ sealed class Basic(
|
|||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.CURRENCY, currency.value)
|
||||
put(AnalyticsParam.BATCH, batch)
|
||||
put(AnalyticsParam.Key.CURRENCY, currency.value)
|
||||
put(AnalyticsParam.Key.BATCH, batch)
|
||||
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
|
||||
put("Sign in type", signInType.name)
|
||||
put("Wallets Count", walletsCount)
|
||||
|
|
@ -39,10 +39,37 @@ sealed class Basic(
|
|||
}
|
||||
}
|
||||
|
||||
class SignedIn(
|
||||
signInType: SignInType,
|
||||
walletsCount: Int,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = buildMap {
|
||||
put("Sign in type", signInType.value)
|
||||
put("Wallets Count", walletsCount.toString())
|
||||
},
|
||||
) {
|
||||
enum class SignInType(val value: String) {
|
||||
Card("Card"),
|
||||
Biometric("Biometric"),
|
||||
NoSecurity("No Security"),
|
||||
AccessCode("Access Code"),
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonBuy(
|
||||
source: AnalyticsParam.ScreensSources,
|
||||
) : Basic(
|
||||
event = "Button - Buy",
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.Key.SOURCE, source.value)
|
||||
},
|
||||
)
|
||||
|
||||
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
|
||||
Basic(
|
||||
event = "Topped up",
|
||||
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
|
||||
params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value),
|
||||
),
|
||||
OneTimeAnalyticsEvent {
|
||||
|
||||
|
|
@ -53,16 +80,16 @@ sealed class Basic(
|
|||
Basic(
|
||||
event = "Transaction sent",
|
||||
params = buildMap {
|
||||
this[AnalyticsParam.SOURCE] = sentFrom.value
|
||||
this[AnalyticsParam.Key.SOURCE] = sentFrom.value
|
||||
if (sentFrom is AnalyticsParam.TxData) {
|
||||
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
|
||||
this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token
|
||||
this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain
|
||||
this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token
|
||||
sentFrom.feeType?.value?.let {
|
||||
this[AnalyticsParam.FEE_TYPE] = it
|
||||
this[AnalyticsParam.Key.FEE_TYPE] = it
|
||||
}
|
||||
}
|
||||
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
||||
this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType
|
||||
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
|
||||
}
|
||||
this["Memo"] = memoType.name
|
||||
},
|
||||
|
|
@ -79,7 +106,7 @@ sealed class Basic(
|
|||
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
|
||||
event = "Request Support",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
AnalyticsParam.Key.SOURCE to source.value,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -89,7 +116,7 @@ sealed class Basic(
|
|||
) : Basic(
|
||||
event = "Biometry Failed",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
AnalyticsParam.Key.SOURCE to source.value,
|
||||
"Reason" to reason.value,
|
||||
),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -36,26 +36,34 @@ sealed class MainScreenAnalyticsEvent(
|
|||
},
|
||||
)
|
||||
|
||||
data object ButtonReceive : MainScreenAnalyticsEvent(
|
||||
class ButtonReceive : MainScreenAnalyticsEvent(
|
||||
event = "Button - Receive",
|
||||
)
|
||||
|
||||
data object LimitsClicked : MainScreenAnalyticsEvent(
|
||||
class LimitsClicked : MainScreenAnalyticsEvent(
|
||||
event = "Limits Clicked",
|
||||
)
|
||||
|
||||
data object NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
||||
class NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Balances Info",
|
||||
)
|
||||
|
||||
data object NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
||||
class NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Limits Info",
|
||||
)
|
||||
|
||||
data object ButtonExplore : MainScreenAnalyticsEvent(
|
||||
class ButtonExplore : MainScreenAnalyticsEvent(
|
||||
event = "Button - Explore",
|
||||
)
|
||||
|
||||
class AccountShowTokens : MainScreenAnalyticsEvent(
|
||||
event = "Button - Account Show Tokens",
|
||||
)
|
||||
|
||||
class AccountHideTokens : MainScreenAnalyticsEvent(
|
||||
event = "Button - Account Hide Tokens",
|
||||
)
|
||||
|
||||
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Swap",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
|
|
@ -66,11 +74,11 @@ sealed class MainScreenAnalyticsEvent(
|
|||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
)
|
||||
|
||||
data object BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
|
||||
class BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
|
||||
|
||||
data object SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
|
||||
class SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
|
||||
|
||||
data object SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
|
||||
class SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
|
||||
|
||||
data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Buy Token Clicked",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.core.analytics.models.event
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
|
||||
import com.tangem.core.analytics.models.AppsFlyerOnlyEvent
|
||||
|
||||
sealed class OnboardingAnalyticsEvent(
|
||||
category: String,
|
||||
|
|
@ -12,11 +14,100 @@ sealed class OnboardingAnalyticsEvent(
|
|||
sealed class Onboarding(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : OnboardingAnalyticsEvent(category = "Onboarding", event = event, params = params) {
|
||||
|
||||
class AppsFlyerOnlyEntryScreenView : Onboarding(event = "wallet_entry_screen_view"), AppsFlyerOnlyEvent
|
||||
|
||||
class Started(
|
||||
source: String,
|
||||
) : Onboarding(
|
||||
event = "Onboarding Started",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source,
|
||||
),
|
||||
)
|
||||
|
||||
class Finished(
|
||||
source: String,
|
||||
) : Onboarding(
|
||||
event = "Onboarding Finished",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source,
|
||||
),
|
||||
)
|
||||
|
||||
class ButtonMobileWallet(
|
||||
source: String,
|
||||
) : Onboarding(
|
||||
event = "Button - Mobile Wallet",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class CreateWallet(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : OnboardingAnalyticsEvent(category = "Onboarding / Create Wallet", event = event, params = params) {
|
||||
|
||||
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
||||
|
||||
class WalletCreatedSuccessfully(
|
||||
source: String,
|
||||
creationType: WalletCreationType = WalletCreationType.NewSeed,
|
||||
seedPhraseLength: Int? = null,
|
||||
passPhraseState: AnalyticsParam.EmptyFull,
|
||||
) : CreateWallet(
|
||||
event = "Wallet Created Successfully",
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.SOURCE, source)
|
||||
put("Creation Type", creationType.value)
|
||||
put("Passphrase", passPhraseState.value)
|
||||
if (seedPhraseLength != null) {
|
||||
put("Seed Phrase Length", seedPhraseLength.toString())
|
||||
}
|
||||
},
|
||||
), AppsFlyerIncludedEvent {
|
||||
override val appsFlyerReplacedEvent = when (creationType) {
|
||||
WalletCreationType.NewSeed -> "wallet_created_successfully"
|
||||
WalletCreationType.SeedImport -> "wallet_imported"
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
data object NewSeed : WalletCreationType(value = "New Seed")
|
||||
data object SeedImport : WalletCreationType(value = "Seed Import")
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SeedPhrase(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : OnboardingAnalyticsEvent(category = "Onboarding / Seed Phrase", event = event, params = params) {
|
||||
|
||||
class CreateMobileScreenOpened(
|
||||
source: String,
|
||||
) : SeedPhrase(
|
||||
event = "Create Mobile Screen Opened",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source,
|
||||
),
|
||||
)
|
||||
|
||||
class ButtonImportWallet : SeedPhrase("Button - Import Wallet")
|
||||
class ImportSeedPhraseScreenOpened : SeedPhrase("Import Seed Phrase Screen Opened")
|
||||
class ButtonImport : SeedPhrase("Button - Import")
|
||||
}
|
||||
|
||||
sealed class Error(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : OnboardingAnalyticsEvent(category = "Error", event = event, params = params) {
|
||||
|
||||
data class OfflineAttestationFailed(
|
||||
val source: AnalyticsParam.ScreensSources,
|
||||
) : Onboarding(
|
||||
) : Error(
|
||||
event = "Offline Attestation Failed",
|
||||
params = mapOf(AnalyticsParam.SOURCE to source.value),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.core.analytics.models.event
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
||||
sealed class SignIn(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent("Sign In", event, params) {
|
||||
|
||||
data class ScreenOpened(
|
||||
val walletsCount: Int,
|
||||
) : SignIn(
|
||||
event = "Sign In Screen Opened",
|
||||
params = mapOf(
|
||||
"Wallets Count" to walletsCount.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
||||
|
||||
class ButtonUnlockAllWithBiometric : SignIn(event = "Button - Unlock All With Biometric")
|
||||
|
||||
data class ErrorBiometricUpdated(
|
||||
val isFromUnlockAll: Boolean,
|
||||
) : SignIn(event = "Error - Biometric Updated")
|
||||
|
||||
class ButtonWallet(
|
||||
signInType: SignInType,
|
||||
walletsCount: Int,
|
||||
) : SignIn(
|
||||
event = "Button - Wallet",
|
||||
params = buildMap {
|
||||
put("Wallets Count", walletsCount.toString())
|
||||
put("Sign in type", signInType.value)
|
||||
},
|
||||
) {
|
||||
enum class SignInType(val value: String) {
|
||||
Card("Card"),
|
||||
Biometric("Biometric"),
|
||||
NoSecurity("No Security"),
|
||||
AccessCode("Access Code"),
|
||||
}
|
||||
}
|
||||
|
||||
data class ButtonAddWallet(
|
||||
val sources: AnalyticsParam.ScreensSources,
|
||||
) : SignIn(
|
||||
event = "Button - Add Wallet",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to sources.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -27,11 +27,7 @@ interface AnalyticsHandler : AnalyticsEventHandler {
|
|||
|
||||
fun id(): String
|
||||
|
||||
fun send(eventId: String, params: Map<String, String> = emptyMap())
|
||||
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
send(event.id, event.params)
|
||||
}
|
||||
override fun send(event: AnalyticsEvent)
|
||||
}
|
||||
|
||||
interface AnalyticsHandlerHolder {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.core.analytics.filter
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
|
||||
import com.tangem.core.analytics.models.AppsFlyerOnlyEvent
|
||||
|
||||
class AppsFlyerEventFilter : AnalyticsEventFilter {
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean =
|
||||
event is AppsFlyerOnlyEvent || event is AppsFlyerIncludedEvent
|
||||
|
||||
override suspend fun canBeSent(event: AnalyticsEvent): Boolean = true
|
||||
|
||||
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
|
||||
return when (event) {
|
||||
is AppsFlyerOnlyEvent -> handler.id() == "AppsFlyer"
|
||||
is AppsFlyerIncludedEvent -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,12 +33,16 @@
|
|||
},
|
||||
{
|
||||
"name": "HOT_WALLET_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.32.0"
|
||||
},
|
||||
{
|
||||
"name": "TANGEM_PAY_ENABLED",
|
||||
"version": "5.31.0"
|
||||
},
|
||||
{
|
||||
"name": "TANGEM_PAY_ENTRYPOINT_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NEW_TOKEN_RECEIVE_ENABLED",
|
||||
"version": "5.28.0"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ enum class ApiEnvironment {
|
|||
@Json(name = "STAGE")
|
||||
STAGE,
|
||||
|
||||
@Json(name = "STAGE_2")
|
||||
STAGE_2,
|
||||
|
||||
@Json(name = "MOCK")
|
||||
MOCK,
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal class Express(
|
|||
createDev2Environment(),
|
||||
createDev3Environment(),
|
||||
createStageEnvironment(),
|
||||
createStage2Environment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
|
@ -73,6 +74,12 @@ internal class Express(
|
|||
headers = createHeaders(isProd = false),
|
||||
)
|
||||
|
||||
private fun createStage2Environment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE_2,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(isProd = false),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
|
|
@ -22,12 +23,7 @@ internal class P2PEthPool(
|
|||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal class TangemPay(
|
|||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "https://api.dev.us.paera.com/bff/",
|
||||
baseUrl = "https://api.dev.us.paera.com/bff-v2/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ internal class TangemPay(
|
|||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.us.paera.com/bff/",
|
||||
baseUrl = "https://api.us.paera.com/bff-v2/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ internal class YieldSupply(
|
|||
ApiEnvironment.DEV_2,
|
||||
ApiEnvironment.DEV_3,
|
||||
ApiEnvironment.STAGE,
|
||||
ApiEnvironment.STAGE_2,
|
||||
-> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev
|
||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey
|
||||
} ?: error("No tangem tech api config provided")
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ interface P2PEthPoolApi {
|
|||
* @param network Ethereum pool network: "mainnet" or "hoodi" (testnet)
|
||||
*/
|
||||
@GET("api/v1/staking/pool/{network}/vaults")
|
||||
suspend fun getVaults(
|
||||
@Path("network") network: String = "mainnet",
|
||||
): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
|
||||
suspend fun getVaults(@Path("network") network: String): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
|
||||
|
||||
/**
|
||||
* Prepare deposit transaction
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ data class TokenMarketListResponse(
|
|||
@Json(name = "market_cap") val marketCap: BigDecimal?,
|
||||
@Json(name = "is_under_market_cap_limit") val isUnderMarketCapLimit: Boolean?,
|
||||
@Json(name = "staking_opportunities") val stakingOpportunities: List<StakingOpportunities>?,
|
||||
@Json(name = "max_yield_apy") val maxYieldApy: BigDecimal?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,6 @@ interface NewsApi {
|
|||
|
||||
private companion object {
|
||||
|
||||
private const val NEWS_PATH = "api/v1/news"
|
||||
private const val NEWS_PATH = "v1/news"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,5 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingResponse(
|
||||
@Json(name = "meta") val meta: NewsTrendingMetaDto,
|
||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingMetaDto(
|
||||
@Json(name = "limit") val limit: Int,
|
||||
)
|
||||
|
|
@ -3,109 +3,13 @@ package com.tangem.datasource.api.pay
|
|||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.pay.models.request.*
|
||||
import com.tangem.datasource.api.pay.models.response.*
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemPayApi {
|
||||
|
||||
// region: auth
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardWallet(
|
||||
@Body request: GenerateNoneByCardWalletRequest,
|
||||
): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getTokenByCustomerWallet(@Body request: GetTokenByCustomerWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCustomerWalletAccessToken(
|
||||
@Body request: RefreshCustomerWalletAccessTokenRequest,
|
||||
): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/exchange")
|
||||
suspend fun exchangeAccessToken(@Body request: ExchangeAccessTokenRequest): ApiResponse<JWTResponse>
|
||||
|
||||
// endregion
|
||||
|
||||
// region: activation
|
||||
|
||||
@POST("v1/activation/status")
|
||||
suspend fun getRemoteActivationStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: ActivationStatusRequest,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCardWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: GetCardWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCustomerWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: GetCustomerWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCardWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCardWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCustomerWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCustomerWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("v1/activation/pin")
|
||||
suspend fun setPinCode(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinCodeRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
// endregion
|
||||
|
||||
@GET("customer/info")
|
||||
suspend fun getCustomerInfo(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("card_id") cardId: String,
|
||||
): ApiResponse<VisaCustomerInfo>
|
||||
|
||||
@GET("product_instance/transactions")
|
||||
suspend fun getTxHistory(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("offset") offset: Int,
|
||||
): ApiResponse<VisaTxHistoryResponse>
|
||||
|
||||
@GET("v1/customer/transactions")
|
||||
suspend fun getTangemPayTxHistory(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
@ -125,9 +29,18 @@ interface TangemPayApi {
|
|||
@Path("customer_wallet_id") customerWalletId: String,
|
||||
): ApiResponse<CheckCustomerWalletResponse>
|
||||
|
||||
@PATCH("v1/customer/pay-enabled")
|
||||
suspend fun setTangemPayEnabledStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetTangemPayEnabledRequest,
|
||||
): ApiResponse<Any>
|
||||
|
||||
@POST("v1/deeplink/validate")
|
||||
suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse<DeeplinkValidityResponse>
|
||||
|
||||
@GET("v1/customer/eligibility")
|
||||
suspend fun checkCustomerEligibility(): ApiResponse<CustomerEligibilityResponse>
|
||||
|
||||
@GET("v1/order/{order_id}")
|
||||
suspend fun getOrder(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
@ -149,6 +62,12 @@ interface TangemPayApi {
|
|||
@Body body: CardDetailsRequest,
|
||||
): ApiResponse<CardDetailsResponse>
|
||||
|
||||
@GET("v1/customer/card/pin")
|
||||
suspend fun getPin(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Header("X-Session-Id") sessionId: String,
|
||||
): ApiResponse<GetPinResponse>
|
||||
|
||||
@PUT("v1/customer/card/pin")
|
||||
suspend fun setPin(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetPinResponse(@Json(name = "result") val result: Result?) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "secret") val secret: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SetTangemPayEnabledRequest(
|
||||
@Json(name = "is_tangem_pay_enabled") val isTangemPayEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -4,5 +4,4 @@ import com.squareup.moshi.Json
|
|||
|
||||
data class CardBalanceResponse(
|
||||
@Json(name = "result") val result: BalanceResponse?,
|
||||
@Json(name = "error") val error: String?,
|
||||
)
|
||||
|
|
@ -5,7 +5,6 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
data class CardDetailsResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -5,5 +5,10 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CheckCustomerWalletResponse(
|
||||
@Json(name = "id") val id: String?,
|
||||
)
|
||||
@Json(name = "result") val result: Result?,
|
||||
) {
|
||||
data class Result(
|
||||
@Json(name = "id") val id: String?,
|
||||
@Json(name = "is_tangem_pay_enabled") val isTangemPayEnabled: Boolean?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerEligibilityResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "is_tangem_pay_available") val isTangemPayAvailable: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,17 +6,16 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerMeResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "state") val state: String,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "created_at") val createdAt: String,
|
||||
@Json(name = "product_instance") val productInstance: ProductInstance?,
|
||||
@Json(name = "payment_account") val paymentAccount: PaymentAccount?,
|
||||
@Json(name = "kyc") val kyc: Kyc?,
|
||||
@Json(name = "depositAddress") val depositAddress: String?,
|
||||
@Json(name = "deposit_address") val depositAddress: String?,
|
||||
@Json(name = "card") val card: Card?,
|
||||
@Json(name = "balance") val balance: BalanceResponse?,
|
||||
)
|
||||
|
|
@ -24,49 +23,49 @@ data class CustomerMeResponse(
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class ProductInstance(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "cid") val cid: String,
|
||||
@Json(name = "cid") val cid: String?,
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String?,
|
||||
@Json(name = "status") val status: Status,
|
||||
@Json(name = "updated_at") val updatedAt: String,
|
||||
@Json(name = "payment_account_id") val paymentAccountId: String,
|
||||
) {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Status {
|
||||
@Json(name = "new")
|
||||
@Json(name = "NEW")
|
||||
NEW,
|
||||
|
||||
@Json(name = "ready_for_manufacturing")
|
||||
@Json(name = "READY_FOR_MANUFACTURING")
|
||||
READY_FOR_MANUFACTURING,
|
||||
|
||||
@Json(name = "manufacturing")
|
||||
@Json(name = "MANUFACTURING")
|
||||
MANUFACTURING,
|
||||
|
||||
@Json(name = "sent_to_delivery")
|
||||
@Json(name = "SENT_TO_DELIVERY")
|
||||
SENT_TO_DELIVERY,
|
||||
|
||||
@Json(name = "delivered")
|
||||
@Json(name = "DELIVERED")
|
||||
DELIVERED,
|
||||
|
||||
@Json(name = "activating")
|
||||
@Json(name = "ACTIVATING")
|
||||
ACTIVATING,
|
||||
|
||||
@Json(name = "active")
|
||||
@Json(name = "ACTIVE")
|
||||
ACTIVE,
|
||||
|
||||
@Json(name = "blocked")
|
||||
@Json(name = "BLOCKED")
|
||||
BLOCKED,
|
||||
|
||||
@Json(name = "deactivating")
|
||||
@Json(name = "DEACTIVATING")
|
||||
DEACTIVATING,
|
||||
|
||||
@Json(name = "deactivated")
|
||||
@Json(name = "DEACTIVATED")
|
||||
DEACTIVATED,
|
||||
|
||||
@Json(name = "canceled")
|
||||
@Json(name = "CANCELED")
|
||||
CANCELED,
|
||||
|
||||
@Json(name = "unknown")
|
||||
@Json(name = "UNKNOWN")
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -91,11 +90,12 @@ data class CustomerMeResponse(
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class Card(
|
||||
@Json(name = "token") val token: String,
|
||||
@Json(name = "expiration_month") val expirationMonth: Int,
|
||||
@Json(name = "expiration_year") val expirationYear: Int,
|
||||
@Json(name = "expiration_month") val expirationMonth: String,
|
||||
@Json(name = "expiration_year") val expirationYear: String,
|
||||
@Json(name = "emboss_name") val embossName: String,
|
||||
@Json(name = "card_type") val cardType: String,
|
||||
@Json(name = "card_status") val cardStatus: String,
|
||||
@Json(name = "card_number_end") val cardNumberEnd: String,
|
||||
@Json(name = "is_pin_set") val isPinSet: Boolean?,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class DeeplinkValidityResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
class FreezeUnfreezeCardResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class OrderResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class VisaErrorResponse(
|
||||
data class TangemPayErrorResponse(
|
||||
@Json(name = "error") val error: Error,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
@ -7,7 +7,6 @@ import java.math.BigDecimal
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TangemPayTxHistoryResponse(
|
||||
@Json(name = "error") val error: String?,
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class WithdrawDataResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class WithdrawResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ interface TangemTechApi {
|
|||
@Body userTokens: UserTokensResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@PUT("/v1/wallets/{walletId}/tokens")
|
||||
suspend fun saveTokens(
|
||||
@Path(value = "walletId") userId: String,
|
||||
@Body userTokens: UserTokensResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
/** Returns referral status by [walletId] */
|
||||
@GET("v1/referral/{walletId}")
|
||||
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
|
||||
|
|
@ -129,6 +135,12 @@ interface TangemTechApi {
|
|||
@Body body: List<WalletIdBody>,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@PUT("/v1/user-wallets/applications/{application_id}/wallets")
|
||||
suspend fun associateApplicationIdWithWalletsV2(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: AssociateApplicationIdWithWalletsBody,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("v1/user-wallets/wallets/{wallet_id}")
|
||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssociateAppWithWalletsErrorResponse(
|
||||
@Json(name = "missingWalletIds") val missingWalletIds: List<String>,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssociateApplicationIdWithWalletsBody(
|
||||
@Json(name = "walletIds") val walletIds: List<String>,
|
||||
)
|
||||
|
|
@ -19,6 +19,7 @@ data class GetWalletAccountsResponse(
|
|||
@Json(name = "group") val group: GroupType?,
|
||||
@Json(name = "sort") val sort: SortType?,
|
||||
@Json(name = "totalAccounts") val totalAccounts: Int,
|
||||
@Json(name = "totalArchivedAccounts") val totalArchivedAccounts: Int,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.SetPinCodeRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ExchangeAccessTokenRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardIdRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
|
||||
import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse
|
||||
import com.tangem.datasource.api.visa.models.response.JWTResponse
|
||||
import com.tangem.datasource.api.visa.models.response.VisaCustomerInfo
|
||||
import com.tangem.datasource.api.visa.models.response.VisaDataToSignResponse
|
||||
import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface VisaApi {
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardWallet(
|
||||
@Body request: GenerateNoneByCardWalletRequest,
|
||||
): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/exchange")
|
||||
suspend fun exchangeAccessToken(@Body request: ExchangeAccessTokenRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/activation/status")
|
||||
suspend fun getRemoteActivationStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: ActivationStatusRequest,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCardWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: GetCardWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCustomerWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body request: GetCustomerWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCardWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCardWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCustomerWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCustomerWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("v1/activation/pin")
|
||||
suspend fun setPinCode(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinCodeRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("customer/info")
|
||||
suspend fun getCustomerInfo(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("card_id") cardId: String,
|
||||
): ApiResponse<VisaCustomerInfo>
|
||||
|
||||
@GET("product_instance/transactions")
|
||||
suspend fun getTxHistory(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("offset") offset: Int,
|
||||
): ApiResponse<VisaTxHistoryResponse>
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -15,4 +15,7 @@ interface AppCurrencyResponseStore {
|
|||
|
||||
/** Get [CurrenciesResponse.Currency] synchronously or null */
|
||||
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
|
||||
|
||||
/** Store [CurrenciesResponse.Currency] */
|
||||
suspend fun store(currency: CurrenciesResponse.Currency)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
|
@ -25,4 +26,11 @@ internal class DefaultAppCurrencyResponseStore(
|
|||
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(currency: CurrenciesResponse.Currency) {
|
||||
appPreferencesStore.storeObject(
|
||||
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||
currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
|||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.MoonPay
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.DevApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.MockApiConfigsManager
|
||||
|
|
@ -21,6 +20,7 @@ import com.tangem.datasource.api.pay.TangemPayAuthApi
|
|||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.api.visa.VisaApi
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder.Timeouts
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -129,7 +129,16 @@ internal object NetworkModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemVisaApi(retrofitApiBuilder: RetrofitApiBuilder): TangemPayApi {
|
||||
fun provideTangemPayApi(retrofitApiBuilder: RetrofitApiBuilder): TangemPayApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.TangemPay,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVisaApi(retrofitApiBuilder: RetrofitApiBuilder): VisaApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.TangemPay,
|
||||
applyTimeoutAnnotations = false,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
|
|
@ -77,6 +78,24 @@ internal object StakingStoreModule {
|
|||
return DefaultStakingActionsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideP2PBalancesPersistenceStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DataStore<Map<String, Set<P2PEthPoolAccountResponse>>> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes(valueTypes = setTypes<P2PEthPoolAccountResponse>()),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "p2p_balances") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideP2PEthPoolVaultsStore(
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
package com.tangem.datasource.local.news.trending
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private typealias TrendingCache = Map<String, List<ShortArticle>>
|
||||
private typealias TrendingCache = Map<String, TrendingNews>
|
||||
|
||||
internal class DefaultTrendingNewsStore(
|
||||
private val store: RuntimeSharedStore<TrendingCache>,
|
||||
) : TrendingNewsStore {
|
||||
|
||||
override fun get(key: String): Flow<List<ShortArticle>> {
|
||||
return store.get().map { it[key].orEmpty() }
|
||||
override fun get(key: String): Flow<TrendingNews> {
|
||||
return store.get().map { it[key] ?: TrendingNews.Data(emptyList()) }
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: String): List<ShortArticle>? {
|
||||
override suspend fun getSyncOrNull(key: String): TrendingNews? {
|
||||
return store.getSyncOrNull()?.get(key)
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: List<ShortArticle>) {
|
||||
override suspend fun store(key: String, value: TrendingNews) {
|
||||
store.update(emptyMap()) { current ->
|
||||
current + (key to value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.datasource.local.news.trending
|
||||
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TrendingNewsStore {
|
||||
|
||||
fun get(key: String): Flow<List<ShortArticle>>
|
||||
fun get(key: String): Flow<TrendingNews>
|
||||
|
||||
suspend fun getSyncOrNull(key: String): List<ShortArticle>?
|
||||
suspend fun getSyncOrNull(key: String): TrendingNews?
|
||||
|
||||
suspend fun store(key: String, value: List<ShortArticle>)
|
||||
suspend fun store(key: String, value: TrendingNews)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ object PreferencesKeys {
|
|||
|
||||
val SAVE_USER_WALLETS_KEY by lazy { booleanPreferencesKey(name = "saveUserWallets") }
|
||||
|
||||
val ROOT_DETECTED_WARNING_SHOWN_KEY by lazy { booleanPreferencesKey(name = "rootDetectedWarningShown") }
|
||||
|
||||
val SHOULD_SHOW_ASK_BIOMETRY_KEY by lazy { booleanPreferencesKey("saveUserWalletShown") }
|
||||
|
||||
val APP_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "launchCount") }
|
||||
|
|
@ -77,8 +79,8 @@ object PreferencesKeys {
|
|||
|
||||
val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") }
|
||||
|
||||
val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
|
||||
booleanPreferencesKey(name = "marketsStakingNotificationHideClicked")
|
||||
val MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
|
||||
booleanPreferencesKey(name = "marketsYieldSupplyNotificationHideClicked")
|
||||
}
|
||||
|
||||
val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") }
|
||||
|
|
@ -108,6 +110,8 @@ object PreferencesKeys {
|
|||
|
||||
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
|
||||
|
||||
val ONRAMP_HANDLED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "onrampHandledTransactions") }
|
||||
|
||||
val ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY by lazy { stringPreferencesKey(name = "onboardingFinalizeScanResponse") }
|
||||
|
||||
val IS_GOOGLE_SERVICES_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGoogleServicesAvailable") }
|
||||
|
|
@ -150,6 +154,7 @@ object PreferencesKeys {
|
|||
}
|
||||
|
||||
val TANGEM_PAY_WITHDRAW_ORDERS_KEY by lazy { stringPreferencesKey(name = "tangemPayWithdrawOrders") }
|
||||
val TANGEM_PAY_ELIGIBILITY_KEY by lazy { booleanPreferencesKey(name = "tangemPayEligibility") }
|
||||
|
||||
fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key")
|
||||
// endregion
|
||||
|
|
@ -192,6 +197,9 @@ object PreferencesKeys {
|
|||
fun getTangemPayCheckCustomerByWalletId(userWalletId: UserWalletId) =
|
||||
booleanPreferencesKey("tangem_pay_check_customer_by_wallet_id_$userWalletId")
|
||||
|
||||
fun getTangemPayHideOnboardingKey(userWalletId: UserWalletId) =
|
||||
booleanPreferencesKey("tangem_pay_hide_onboarding_key_$userWalletId")
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,28 +4,32 @@ import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
|||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.staking.BalanceItem
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.YieldBalanceItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
class YieldBalanceConverter(
|
||||
/**
|
||||
* Converts StakeKit DTO to [StakingBalance].
|
||||
* Returns [StakingBalance.Data.StakeKit] for non-empty balances, [StakingBalance.Empty] otherwise.
|
||||
*/
|
||||
class StakingBalanceConverter(
|
||||
private val source: StatusSource,
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance?> {
|
||||
) : Converter<YieldBalanceWrapperDTO, StakingBalance?> {
|
||||
|
||||
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
|
||||
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? {
|
||||
override fun convert(value: YieldBalanceWrapperDTO): StakingBalance? {
|
||||
val stakingId = StakingID(
|
||||
integrationId = value.integrationId ?: return null,
|
||||
address = value.addresses.address,
|
||||
)
|
||||
|
||||
return if (value.balances.isEmpty()) {
|
||||
YieldBalance.Empty(stakingId = stakingId, source = source)
|
||||
StakingBalance.Empty(stakingId = stakingId, source = source)
|
||||
} else {
|
||||
YieldBalance.Data(
|
||||
StakingBalance.Data.StakeKit(
|
||||
stakingId = stakingId,
|
||||
balance = YieldBalanceItem(
|
||||
items = value.balances
|
||||
|
|
@ -3,14 +3,18 @@ package com.tangem.datasource.local.visa
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemPayStorage {
|
||||
|
||||
suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String?
|
||||
|
||||
suspend fun clearCustomerWalletAddress(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens)
|
||||
|
||||
suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens?
|
||||
suspend fun clearAuthTokens(customerWalletAddress: String)
|
||||
|
||||
suspend fun storeOrderId(customerWalletAddress: String, orderId: String)
|
||||
|
||||
|
|
@ -30,5 +34,11 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun deleteWithdrawOrder(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean)
|
||||
suspend fun storeTangemPayEligibility(eligibility: Boolean)
|
||||
suspend fun getTangemPayEligibility(): Boolean
|
||||
|
||||
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_
|
|||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
|
||||
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
|
||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
|
|
@ -250,7 +251,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
id = ApiConfig.ID.TangemPay,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "https://api.dev.us.paera.com/bff/",
|
||||
baseUrl = "https://api.dev.us.paera.com/bff-v2/",
|
||||
headers = mapOf(
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
|
|
@ -299,11 +300,17 @@ internal class ProdApiConfigsManagerTest {
|
|||
}
|
||||
|
||||
private fun createP2PModel(): TestModel {
|
||||
val (environment, baseUrl) = if (P2PStakingConfig.USE_TESTNET) {
|
||||
ApiEnvironment.DEV to "https://api-test.p2p.org/"
|
||||
} else {
|
||||
ApiEnvironment.PROD to "https://api.p2p.org/"
|
||||
}
|
||||
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.P2PEthPool,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.p2p.org/",
|
||||
environment = environment,
|
||||
baseUrl = baseUrl,
|
||||
headers = mapOf(
|
||||
"Authorization" to ProviderSuspend { "Bearer $P2P_API_KEY" },
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
|
|
|
|||
22
core/pagination/detekt-baseline-main.xml
Normal file
22
core/pagination/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:BatchAction.kt$BatchAction.UpdateBatches$val async: Boolean = false</ID>
|
||||
<ID>BooleanPropertyNaming:BatchFetchResult.kt$BatchFetchResult.Success$val empty: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:BatchFetchResult.kt$BatchFetchResult.Success$val last: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:BatchListSource.kt$DefaultBatchListSource$val started = job.start()</ID>
|
||||
<ID>MultilineLambdaItParameter:BatchListSource.kt$DefaultBatchListSource${ currentCoroutineContext().ensureActive() BatchFetchResult.Error(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:BatchListSource.kt$DefaultBatchListSource${ if (predicate(it.first)) { it.second.cancel() null } else { it } }</ID>
|
||||
<ID>MultilineLambdaItParameter:CursorBatchFetcher.kt$CursorBatchFetcher${ currentCoroutineContext().ensureActive() return BatchFetchResult.Error(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:LimitOffsetBatchFetcher.kt$LimitOffsetBatchFetcher${ currentCoroutineContext().ensureActive() BatchFetchResult.Error(it) }</ID>
|
||||
<ID>NamedArguments:BatchListSource.kt$DefaultBatchListSource(fetchDispatcher, context, generateNewKey, batchFetcher, null)</ID>
|
||||
<ID>NamedArguments:BatchListSource.kt$DefaultBatchListSource(fetchDispatcher, context, generateNewKey, batchFetcher, updateFetcher)</ID>
|
||||
<ID>NestedScopeFunctions:BatchListSource.kt$DefaultBatchListSource$also { currentCoroutineContext().ensureActive() }</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:BatchListSource.kt$DefaultBatchListSource$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:CursorBatchFetcher.kt$CursorBatchFetcher$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:LimitOffsetBatchFetcher.kt$LimitOffsetBatchFetcher$runCatching</ID>
|
||||
<ID>UseEmptyCounterpart:BatchListSource.kt$DefaultBatchListSource$listOf()</ID>
|
||||
<ID>UseOrEmpty:BatchListSource.kt$DefaultBatchListSource$batch?.let { listOf(it) } ?: emptyList()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<string name="access_code_alert_skip_ok">Trotzdem überspringen</string>
|
||||
<string name="access_code_alert_skip_title">Zugangscode nicht festgelegt</string>
|
||||
<string name="access_code_alert_validation_cancel">Code ändern</string>
|
||||
<string name="access_code_alert_validation_description">Dein Zugangscode dient zum Entsperren Deiner Wallet und zum Schutz des Zugriffs auf Deine Vermögenswerte.</string>
|
||||
<string name="access_code_alert_validation_description">Dein Zugangscode entsperrt und schützt den Zugriff auf Deine Geldbörse.</string>
|
||||
<string name="access_code_alert_validation_ok">Trotzdem verwenden</string>
|
||||
<string name="access_code_alert_validation_title">Dieser Zugangscode kann leicht erraten werden</string>
|
||||
<string name="access_code_check_title">Zugangscode eingeben</string>
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
<string name="account_form_placeholder_new_account">Neues Konto</string>
|
||||
<string name="account_form_title_create">Konto hinzufügen</string>
|
||||
<string name="account_form_title_edit">Konto bearbeiten</string>
|
||||
<string name="account_generic_error_dialog_message">Bitte versuche es später erneut. Sollte das Problem weiterhin bestehen, kontaktiere bitte unseren Support. Wir helfen Dir gerne bei der Lösung.</string>
|
||||
<string name="account_label_tokens_info">%1$s in %2$s</string>
|
||||
<string name="account_main_account_title">Hauptkonto</string>
|
||||
<string name="account_recover_limit_dialog_description">Du hast das Limit von %1$s aktiven Konten bereits überschritten. Archiviere eines zur Wiederherstellung</string>
|
||||
|
|
@ -96,7 +97,7 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">Tokens im %1$s -Netzwerk werden von dieser Karte oder Ring aufgrund einer Firmware-Einschränkung nicht unterstützt.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">Hast du Probleme beim Scannen deiner Karte oder Ring?</string>
|
||||
<string name="alert_unsupported_card">Diese Karte oder Ring ist für die Zusammenarbeit mit Tangem nicht geeignet</string>
|
||||
<string name="app_settings_access_code_warning">Lege zunächst einen Zugangscode fest, um die Biometrie zu aktivieren.</string>
|
||||
<string name="app_settings_access_code_warning">Lege einen Zugangscode fest, um die Biometrie zu aktivieren.</string>
|
||||
<string name="app_settings_biometrics_footer">Verwende die %1$s um Deine Wallet zu entsperren und sensible Aktionen wie das Signieren von Transaktionen zu bestätigen. Bei Hardware-Wallets ist zum Signieren weiterhin eine Karte oder ein Ring erforderlich.</string>
|
||||
<string name="app_settings_default_fee">Standardgebühr</string>
|
||||
<string name="app_settings_default_fee_footer">Aktiviere die Option Standardgebühr, um die Transaktionsgebühren automatisch festzulegen und die Gebührenseite beim Senden von Geldern zu überspringen. Du kannst bei Bedarf jederzeit zu dieser Seite zurückkehren.</string>
|
||||
|
|
@ -145,10 +146,10 @@
|
|||
<string name="balance_hidden_title">Guthaben sind ausgeblendet</string>
|
||||
<string name="beta_mode_warning_message">Laut den Blockchain-Entwicklern befinden sich der Kaspa-Token derzeit in der Betaphase. Bleibe dran für Updates!</string>
|
||||
<string name="beta_mode_warning_title">Beta-Phase</string>
|
||||
<string name="biometric_disabled_warning_description">Die biometrischen Daten sind auf Deinem Gerät deaktiviert, sodass Du sie nicht zum Entsperren Deine Wallet verwenden kannst. Aktiviere die Biometrie in den Einstellungen Deines Geräts, um diese Methode wieder zu verwenden.</string>
|
||||
<string name="biometric_disabled_warning_description">Die Biometrie ist auf Deinem Gerät deaktiviert, daher kannst Du sie nicht zum Entsperren Deiner Wallets verwenden. Aktiviere die Biometrie in den Geräteeinstellungen, um diese Methode wieder nutzen zu können.</string>
|
||||
<string name="biometric_disabled_warning_title">Biometrische Authentifizierung deaktiviert</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">Bitte Karte oder Ring scannen</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperren Deine Wallet durch Antippen Deines Geräts oder gib den Zugangscode ein.</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperre Deine Wallet mit einer Karte/einem Ring oder gib Deinen Zugangscode ein.</string>
|
||||
<string name="biometric_lockout_permanent_warning_title">Biometrische Authentifizierung gesperrt</string>
|
||||
<string name="biometric_lockout_warning_description">Bitte versuche es in 30 Sekunden erneut oder scanne die Karte oder Ring</string>
|
||||
<string name="biometric_lockout_warning_description_2">Die biometrische Anmeldung ist vorübergehend gesperrt. Bitte versuche es in 30 Sekunden erneut oder entsperre Deine Wallet durch Antippen Deines Geräts oder mit einem Zugangscode.</string>
|
||||
|
|
@ -374,6 +375,7 @@
|
|||
<string name="common_terms_and_conditions">Allgemeine Geschäftsbedingungen</string>
|
||||
<string name="common_terms_of_use">Nutzungsbedingungen</string>
|
||||
<string name="common_to">An</string>
|
||||
<string name="common_to_wallet_name">Zu %s</string>
|
||||
<string name="common_today">Heute</string>
|
||||
<plurals name="common_tokens_count">
|
||||
<item quantity="one">%d Token</item>
|
||||
|
|
@ -385,6 +387,7 @@
|
|||
<string name="common_transfer">Überweisung</string>
|
||||
<string name="common_unable_to_load">Die Daten konnten nicht geladen werden…</string>
|
||||
<string name="common_understand">Ich verstehe</string>
|
||||
<string name="common_understand_continue">Ich verstehe, fahre bitte fort.</string>
|
||||
<string name="common_unknown_error">Es ist ein Fehler aufgetreten. Bitte versuche es erneut.</string>
|
||||
<string name="common_unreachable">Nicht erreichbar</string>
|
||||
<string name="common_unstake">Staking beenden</string>
|
||||
|
|
@ -398,6 +401,7 @@
|
|||
<string name="currency_subtitle_expanded">Verfügbare Netzwerke</string>
|
||||
<string name="custom_token_another_account_dialog_description">Die Ableitung Deines Tokens entspricht der Ableitung von %1$s. Dein Token wird diesem Konto gutgeschrieben.</string>
|
||||
<string name="custom_token_another_account_dialog_title">Die Herleitung stammt aus einem anderen Bericht.</string>
|
||||
<string name="custom_token_another_account_snackbar_text">Token hinzugefügt %1$s Konto</string>
|
||||
<string name="custom_token_contract_address_input_title">Vertragsadresse</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Vertragsadresse ist ungültig</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Bitte wähle das Netzwerk</string>
|
||||
|
|
@ -725,6 +729,7 @@
|
|||
<string name="markets_sort_by_top_gainers_title">Top-Gewinner</string>
|
||||
<string name="markets_sort_by_top_losers_title">Top-Verlierer</string>
|
||||
<string name="markets_sort_by_trending_title">Beliebt</string>
|
||||
<string name="markets_sort_by_yield_mode_title">Ertragsmodus</string>
|
||||
<string name="markets_staking_banner_description_placeholder">Staking ist der einfachste Weg, um Belohnungen für Deine Kryptowährung zu erhalten. %s</string>
|
||||
<string name="markets_staking_banner_title">Verdiene bis zu %s APY</string>
|
||||
<string name="markets_token_added">Token hinzugefügt</string>
|
||||
|
|
@ -800,8 +805,17 @@
|
|||
<string name="mobile_wallet_requires_min_os_warning_title">Mobile Wallet erfordert %1$s oder später</string>
|
||||
<string name="news_all_news">Alle Neuigkeiten</string>
|
||||
<string name="news_like">Gefällt mir</string>
|
||||
<plurals name="news_published_hours_ago">
|
||||
<item quantity="one">%dStunde her</item>
|
||||
<item quantity="other">%dStunden her</item>
|
||||
</plurals>
|
||||
<plurals name="news_published_minutes_ago">
|
||||
<item quantity="one">%dMinute her</item>
|
||||
<item quantity="other">%dMinuten her</item>
|
||||
</plurals>
|
||||
<string name="news_quick_recap">Kurze Zusammenfassung</string>
|
||||
<string name="news_related_news">Verwandte Nachrichten</string>
|
||||
<string name="news_related_tokens">Verwandte Token</string>
|
||||
<string name="news_sources">Quellen</string>
|
||||
<string name="news_stay_in_the_loop">Auf dem Laufenden bleiben</string>
|
||||
<string name="nfc_error_unavailable">NFC ist auf deinem Gerät nicht verfügbar</string>
|
||||
|
|
@ -978,6 +992,7 @@
|
|||
<string name="onramp_currency_other">Andere Währungen</string>
|
||||
<string name="onramp_currency_popular">Beliebte Fiats</string>
|
||||
<string name="onramp_currency_search">Suche nach Währung</string>
|
||||
<string name="onramp_error_transaction_already_processed">Diese Transaktion wurde bereits verarbeitet. Es sind keine weiteren Maßnahmen erforderlich.</string>
|
||||
<string name="onramp_fetching_best_rates">Die besten Preise erzielen...</string>
|
||||
<string name="onramp_instant_status">Sofort</string>
|
||||
<string name="onramp_legal">Durch die Nutzung der Onramp-Funktionalität stimmst Du den %1$s und %2$s des Anbieters zu.</string>
|
||||
|
|
@ -1074,6 +1089,7 @@
|
|||
<string name="reset_card_to_factory_button_title">Karte oder Ring zurücksetzen</string>
|
||||
<string name="reset_card_to_factory_condition_1">Mir ist bewusst, dass ich nach der Durchführung dieser Aktion keinen Zugriff mehr auf die aktuelle Wallet habe.</string>
|
||||
<string name="reset_card_to_factory_condition_2">Mir ist klar, dass ich diese Karte oder Ring nicht verwenden kann, um meinen Zugangscode auf den anderen Karten oder Ringe der aktuellen Wallet wiederherzustellen</string>
|
||||
<string name="reset_card_to_factory_condition_3">Mir ist bewusst, dass ich den Zugang zu meiner Tangem Pay Karte und allen darauf befindlichen Geldern vollständig verliere, ohne die Möglichkeit der Wiederherstellung</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Durch das Zurücksetzen auf Werkseinstellungen wird die Wallet vollständig von der ausgewählten Karte oder Ring gelöscht. Du kannst die aktuelle Wallet nicht wiederherstellen oder die Karte oder Ring verwenden, um den Zugangscode wiederherzustellen.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Beim Zurücksetzen auf die Werkseinstellungen wird die Wallet der ausgewählten Karte oder Ring vollständig gelöscht und aus der App entfernt. Es ist nicht möglich, die aktuelle Wallet wiederherzustellen.</string>
|
||||
<string name="reset_cards_dialog_complete_description">Alle Tangem-Geräte wurden zurückgesetzt.</string>
|
||||
|
|
@ -1227,6 +1243,8 @@
|
|||
<string name="staking_account_initialization_footer">Eine Netzwerkgebühr ist eine kleine Zahlung, die erforderlich ist, um Deine Transaktion auf der Blockchain zu verarbeiten und zu bestätigen.</string>
|
||||
<string name="staking_account_initialization_message">Um mit dem Staking zu beginnen, muss Dein TON-Konto mit einer Transaktion von 1 TON aktiviert werden. Das Guthaben verbleibt auf Deinem Konto, dieser Schritt dient lediglichder aktivierung für das Staking.</string>
|
||||
<string name="staking_account_initialization_title">Kontoaktivierung</string>
|
||||
<string name="staking_alert_network_fee_updated_message">Die Netzwerkgebühr hat sich geändert. Bitte überprüfe den neuen Betrag, bevor Du fortfährst.</string>
|
||||
<string name="staking_alert_network_fee_updated_title">Netzwerkgebühr aktualisiert</string>
|
||||
<string name="staking_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
|
||||
<string name="staking_amount_tron_integer_error">Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet.</string>
|
||||
<string name="staking_amount_tron_integer_error_unstaking">Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet.</string>
|
||||
|
|
@ -1265,6 +1283,7 @@
|
|||
<string name="staking_give_permission_fee_footer">Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst.</string>
|
||||
<string name="staking_legal">Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu.</string>
|
||||
<string name="staking_locked">Gesperrt</string>
|
||||
<string name="staking_max_amount_requirement_error">Höchstbetrag: %s</string>
|
||||
<string name="staking_migrate">Migrieren</string>
|
||||
<string name="staking_native">Natives Staking</string>
|
||||
<string name="staking_no_validators_error_message">Derzeit sind keine aktiven Validatoren für das Staking verfügbar. Bitte versuchen Sie es später erneut.</string>
|
||||
|
|
@ -1417,6 +1436,7 @@
|
|||
<string name="tangem_pay_freeze_card_success">Ihre Karte ist eingefroren.</string>
|
||||
<string name="tangem_pay_get_help">Hilfe erhalten</string>
|
||||
<string name="tangem_pay_other">Andere</string>
|
||||
<string name="tangem_pay_rooted_device_subtitle">Nicht nutzbar auf gerooteten Geräten</string>
|
||||
<string name="tangem_pay_status_completed">Abgeschlossen</string>
|
||||
<string name="tangem_pay_status_declined">Abgelehnt</string>
|
||||
<string name="tangem_pay_status_pending">Ausstehend</string>
|
||||
|
|
@ -1429,6 +1449,7 @@
|
|||
<string name="tangem_pay_unfreeze_card_failed">Entsperren der Karte fehlgeschlagen. Versuchen Sie es später erneut.</string>
|
||||
<string name="tangem_pay_unfreeze_card_success">Ihre Karte ist entsperrt.</string>
|
||||
<string name="tangem_pay_withdrawal">Abhebung</string>
|
||||
<string name="tangempay_account_unable_to_use_rooted">Auf gerooteten Geräten nicht nutzbar.</string>
|
||||
<string name="tangempay_cancel_kyc">KYC abbrechen</string>
|
||||
<string name="tangempay_card_details_add_funds">Guthaben hinzufügen</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">Aufladeoptionen</string>
|
||||
|
|
@ -1466,6 +1487,7 @@
|
|||
<string name="tangempay_card_details_swap_description">Tausche beliebige Vermögenswerte in Deinem Portfolio gegen eine Karte.</string>
|
||||
<string name="tangempay_card_details_title">Kartendetails</string>
|
||||
<string name="tangempay_card_details_unfreeze_card">Karte entsperren</string>
|
||||
<string name="tangempay_card_details_view_pin_code_description">Komm zurück zur App, falls du es vergisst.</string>
|
||||
<string name="tangempay_card_details_view_pin_code_title">Dein PIN-Code</string>
|
||||
<string name="tangempay_card_details_withdraw">Auszahlung</string>
|
||||
<string name="tangempay_card_details_withdraw_error_title">Auszahlung derzeit nicht möglich</string>
|
||||
|
|
@ -1473,6 +1495,7 @@
|
|||
<string name="tangempay_card_details_withdraw_in_progress_title">Auszahlung läuft</string>
|
||||
<string name="tangempay_change_pin_code">PIN-Code ändern</string>
|
||||
<string name="tangempay_come_back_if_forget_pin">Kehren Sie zur App zurück, falls Sie ihn vergessen.</string>
|
||||
<string name="tangempay_factory_settings_warning_title">Mir ist bewusst, dass ich den Zugriff auf meine Tangem Pay Card und alle darauf befindlichen Guthaben vollständig und ohne Möglichkeit der Wiederherstellung verliere.</string>
|
||||
<string name="tangempay_failed_to_issue_card">Kartenausstellung fehlgeschlagen</string>
|
||||
<string name="tangempay_failed_to_issue_card_retry_description">Ein technischer Fehler ist aufgetreten, bitte versuchen Sie es erneut, indem Sie auf die Schaltfläche unten klicken</string>
|
||||
<string name="tangempay_failed_to_issue_card_support_description">Ein technischer Fehler ist aufgetreten, bitte kontaktieren Sie den Support</string>
|
||||
|
|
@ -1491,6 +1514,7 @@
|
|||
<string name="tangempay_kyc_in_progress">KYC in Bearbeitung</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_button">Status anzeigen</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_title">KYC für Tangem Pay in Arbeit</string>
|
||||
<string name="tangempay_kyc_in_progress_popup_description">Über die Schaltflächen unten kannst Du Deinen aktuellen KYC-Status einsehen oder ihn abbrechen.</string>
|
||||
<string name="tangempay_onboarding_banner_description">Holen Sie sich Ihre kostenlose virtuelle Tangem Visa-Karte</string>
|
||||
<string name="tangempay_onboarding_banner_title">Nutzen Sie USDC für alltägliche Zahlungen</string>
|
||||
<string name="tangempay_onboarding_get_card_button_text">Karte erhalten</string>
|
||||
|
|
@ -1503,12 +1527,15 @@
|
|||
<string name="tangempay_onboarding_title">Erhalten Sie Ihre kostenlose Tangem Pay Card in wenigen Minuten</string>
|
||||
<string name="tangempay_payment_account">Zahlungskonto</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Zahlungskonto ist nicht synchronisiert</string>
|
||||
<string name="tangempay_pin_validation_error_message">Ungültige PIN: Sequenzen oder Wiederholungen vermeiden</string>
|
||||
<string name="tangempay_service_unavailable_description">Wir beheben ein technisches Problem. Bitte versuchen Sie es später erneut.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service vorübergehend nicht verfügbar</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin.</string>
|
||||
<string name="tangempay_sync_needed">Synchronisation erforderlich</string>
|
||||
<string name="tangempay_set_pin_code">Satz \nPIN-Code</string>
|
||||
<string name="tangempay_sync_needed">Nicht synchronisiert</string>
|
||||
<string name="tangempay_sync_needed_restore_access">Zugang wiederherstellen</string>
|
||||
<string name="tangempay_tangem_visa_card">Nutzen Sie USDC für alltägliche Zahlungen</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay ist vorübergehend nicht verfügbar</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay ist vorübergehend nicht erreichbar.</string>
|
||||
<string name="tangempay_title">Tangem Pay</string>
|
||||
<string name="tangempay_use_tangem_device_to_restore_payment_account">Klicken Sie auf die Schaltfläche unten, um den Zugriff wiederherzustellen</string>
|
||||
<string name="tangempay_your_pin_code">Ihr PIN-Code</string>
|
||||
|
|
@ -1975,7 +2002,7 @@
|
|||
<string name="xtz_withdrawal_message_warning">Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden</string>
|
||||
<string name="yield_module_alert_description">Wenn der Yield-Modus aktiviert ist, gehen alle zukünftigen Einzahlungen an diese Adresse an Aave. Du kannst über Dein Guthaben weiterhin frei verfügen.</string>
|
||||
<string name="yield_module_alert_title">Deine %s wird an Aave übermittelt</string>
|
||||
<string name="yield_module_amount_not_transfered_to_aave_title">Die Lieferung von %1$s %2$s an Aave steht noch aus.</string>
|
||||
<string name="yield_module_amount_not_transfered_to_aave_title">Lieferung %1$s %2$s nach Aave</string>
|
||||
<string name="yield_module_approve_needed_notification_cta">Genehmigen</string>
|
||||
<string name="yield_module_approve_needed_notification_description">Die Genehmigung Deines Tokens wurde widerrufen. Erteilen diese erneut, um die Servicefunktionalität fortzusetzen.</string>
|
||||
<string name="yield_module_approve_needed_notification_title">Genehmigung erforderlich</string>
|
||||
|
|
@ -2056,6 +2083,7 @@
|
|||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Ertragsmodus</string>
|
||||
<string name="yield_module_token_details_earn_notification_processing">Bearbeitung Deiner Einzahlung</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Ertragsmodus</string>
|
||||
<string name="yield_module_transaction_deploy_contract">Yield-Mode-Vertragsbereitstellung</string>
|
||||
<string name="yield_module_transaction_enter">Ertragsmodus aktivieren</string>
|
||||
<string name="yield_module_transaction_enter_subtitle">%1$s geliefert an Aave</string>
|
||||
<string name="yield_module_transaction_exit">Ertragsmodus deaktiviert</string>
|
||||
|
|
|
|||
|
|
@ -1400,6 +1400,7 @@
|
|||
<string name="tangempay_onboarding_title">Obtén tu tarjeta Tangem Pay gratuita en minutos</string>
|
||||
<string name="tangempay_payment_account">Cuenta de pago</string>
|
||||
<string name="tangempay_payment_account_sync_needed">La cuenta de pago no está sincronizada</string>
|
||||
<string name="tangempay_pin_validation_error_message">PIN no válido: evitar secuencias o repeticiones</string>
|
||||
<string name="tangempay_service_unavailable_description">Estamos solucionando un problema técnico. Por favor, inténtelo de nuevo más tarde.</string>
|
||||
<string name="tangempay_service_unavailable_title">Servicio temporalmente no disponible</string>
|
||||
<string name="tangempay_service_unreachable_try_later">No es posible mostrar los datos en este momento, pero los pagos con tarjeta siguen funcionando.</string>
|
||||
|
|
|
|||
|
|
@ -1392,6 +1392,7 @@
|
|||
<string name="tangempay_onboarding_title">Obtenez votre carte Tangem Pay gratuite en quelques minutes</string>
|
||||
<string name="tangempay_payment_account">Compte de paiement</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Le compte de paiement n\'est pas synchronisé</string>
|
||||
<string name="tangempay_pin_validation_error_message">Code PIN invalide : évitez les séquences ou les répétitions</string>
|
||||
<string name="tangempay_service_unavailable_description">Nous réparons un problème technique. Veuillez réessayer plus tard.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service temporairement indisponible</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours.</string>
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@
|
|||
<string name="tangempay_onboarding_title">Ottieni la tua carta Tangem Pay gratuita in pochi minuti</string>
|
||||
<string name="tangempay_payment_account">Conto di pagamento</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Il conto di pagamento non è sincronizzato</string>
|
||||
<string name="tangempay_pin_validation_error_message">PIN non valido: evitare sequenze o ripetizioni</string>
|
||||
<string name="tangempay_service_unavailable_description">Stiamo risolvendo un problema tecnico. Riprova più tardi.</string>
|
||||
<string name="tangempay_service_unavailable_title">Servizio temporaneamente non disponibile</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Al momento non è possibile visualizzare i dati, ma i pagamenti con carta continuano a funzionare.</string>
|
||||
|
|
|
|||
|
|
@ -1504,6 +1504,7 @@
|
|||
<string name="tangempay_onboarding_title">無料のTangem Payカードを数分でゲットしましょう</string>
|
||||
<string name="tangempay_payment_account">支払いアカウント</string>
|
||||
<string name="tangempay_payment_account_sync_needed">支払アカウントが同期されていません</string>
|
||||
<string name="tangempay_pin_validation_error_message">無効な暗証番号:連続や繰り返しを避けてください</string>
|
||||
<string name="tangempay_service_unavailable_description">技術的な問題を修正しています。後でもう一度お試しください。</string>
|
||||
<string name="tangempay_service_unavailable_title">サービスは一時的に利用できません</string>
|
||||
<string name="tangempay_service_unreachable_try_later">現在、データを表示できませんが、カードでのお支払いは引き続きご利用いただけます。</string>
|
||||
|
|
|
|||
|
|
@ -1532,6 +1532,7 @@
|
|||
<string name="tangempay_onboarding_title">Откройте виртуальную \nTangem Pay Card</string>
|
||||
<string name="tangempay_payment_account">Платежный аккаунт</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Платежный аккаунт не синхронизирован</string>
|
||||
<string name="tangempay_pin_validation_error_message">Слабый ПИН: не используйте повторы или последовательности.</string>
|
||||
<string name="tangempay_service_unavailable_description">Мы устраняем техническую проблему. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="tangempay_service_unavailable_title">Сервис временно недоступен</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Не можем показать данные карты, но оплаты продолжают работать.</string>
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@
|
|||
<string name="account_unsaved_dialog_message_create">Are you sure you want to discard new account?</string>
|
||||
<string name="account_unsaved_dialog_message_edit">Are you sure you want to discard edits?</string>
|
||||
<string name="account_unsaved_dialog_title">Unsaved Changes</string>
|
||||
<string name="accounts_migration_alert_message">Some custom tokens were moved from “%1$s” to “%2$s” as their derivation belongs to that account.</string>
|
||||
<string name="accounts_migration_alert_title">Some custom tokens were moved</string>
|
||||
<string name="accounts_migration_alert_message">Some custom tokens will be automatically moved from “%1$s” to “%2$s” as their derivation belongs to that account.</string>
|
||||
<string name="accounts_migration_alert_title">Some custom tokens will be automatically moved</string>
|
||||
<string name="action_buttons_buy_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for purchase</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for selling.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Sell</string>
|
||||
|
|
@ -816,6 +816,7 @@
|
|||
</plurals>
|
||||
<string name="news_quick_recap">Quick recap</string>
|
||||
<string name="news_related_news">Related News</string>
|
||||
<string name="news_related_tokens">Related tokens</string>
|
||||
<string name="news_sources">Sources</string>
|
||||
<string name="news_stay_in_the_loop">Stay in the loop</string>
|
||||
<string name="nfc_error_unavailable">NFC is not available on your device</string>
|
||||
|
|
@ -1527,9 +1528,11 @@
|
|||
<string name="tangempay_onboarding_title">Get your free Tangem Pay Card in minutes</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Payment account is not synced</string>
|
||||
<string name="tangempay_pin_validation_error_message">Invalid PIN: avoid sequences or repeats</string>
|
||||
<string name="tangempay_service_unavailable_description">We’re fixing a technical issue. Please try again later.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service temporarily unavailable</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Unable to display details. However, card payments are still working.</string>
|
||||
<string name="tangempay_set_pin_code">Set \nPIN code</string>
|
||||
<string name="tangempay_sync_needed">Not synced</string>
|
||||
<string name="tangempay_sync_needed_restore_access">Restore access</string>
|
||||
<string name="tangempay_tangem_visa_card">Use USDC for everyday payments</string>
|
||||
|
|
|
|||
1
core/security/.gitignore
vendored
Normal file
1
core/security/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
4
core/security/build.gradle.kts
Normal file
4
core/security/build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.security
|
||||
|
||||
interface DeviceSecurityInfoProvider {
|
||||
val isRooted: Boolean
|
||||
val isBootloaderUnlocked: Boolean
|
||||
val isXposed: Boolean
|
||||
}
|
||||
|
||||
fun DeviceSecurityInfoProvider.isSecurityExposed(): Boolean = isRooted || isBootloaderUnlocked || isXposed
|
||||
|
|
@ -66,4 +66,6 @@ dependencies {
|
|||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@
|
|||
<ID>NoNameShadowing:TextAnimatedCounter.kt$char</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:InputManager.kt$InputManager$_query</ID>
|
||||
<ID>ReusedModifierInstance:EllipsisText.kt$Text( text = layoutText, color = color, style = style, fontStyle = fontStyle, textDecoration = textDecoration, textAlign = textAlign, softWrap = softWrap, maxLines = 1, onTextLayout = { textLayoutResultState.value = it }, modifier = modifier, )</ID>
|
||||
<ID>ReusedModifierInstance:Label.kt$Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = modifier .padding(horizontal = 4.dp) .clip(TangemTheme.shapes.roundedCorners8) .background(color = backgroundColor) .then( if (state.onClick != null) { Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(), onClick = state.onClick, ) } else { Modifier }, ) .padding(horizontal = 8.dp, vertical = 4.dp), ) { Text( modifier = Modifier.weight(1.0f, fill = false), text = text.resolveReference(), style = TangemTheme.typography.caption1, color = textColor, ) AnimatedVisibility(state.icon != null) { val wrappedIcon = remember(this) { requireNotNull(state.icon) } Icon( imageVector = ImageVector.vectorResource(wrappedIcon), tint = iconColor, contentDescription = null, modifier = Modifier .size(16.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = false), onClick = { state.onIconClick?.invoke() }, ), ) } }</ID>
|
||||
<ID>ReusedModifierInstance:TangemRadioButton.kt$AnimatedVisibility( visible = isSelected, label = "Radio button animation", modifier = modifier .size(TangemTheme.dimens.size24), ) { Icon( painter = painterResource(id = R.drawable.ic_check_circle_24), contentDescription = null, tint = TangemTheme.colors.control.checked, ) }</ID>
|
||||
<ID>ReusedModifierInstance:TokenPrice.kt$Icon( modifier = modifier, painter = painterResource( id = when (animatedType) { PriceChangeType.UP -> R.drawable.ic_arrow_up_8 PriceChangeType.DOWN -> R.drawable.ic_arrow_down_8 PriceChangeType.NEUTRAL -> R.drawable.ic_elipse_8 }, ), tint = when (animatedType) { PriceChangeType.UP -> TangemTheme.colors.icon.accent PriceChangeType.DOWN -> TangemTheme.colors.icon.warning PriceChangeType.NEUTRAL -> TangemTheme.colors.icon.inactive }, contentDescription = null, )</ID>
|
||||
<ID>UnnecessaryEventHandlerParameter:PinTextField.kt$onValueChange: (String) -> Unit</ID>
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ fun SecondaryButtonIconEnd(
|
|||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
|
|
@ -230,6 +232,8 @@ fun SecondaryButtonIconEnd(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
size = size,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +248,8 @@ fun SecondaryButtonIconStart(
|
|||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
|
|
@ -254,6 +260,8 @@ fun SecondaryButtonIconStart(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
size = size,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
// endregion SecondaryButton
|
||||
|
|
|
|||
|
|
@ -37,44 +37,46 @@ fun DialogFullScreen(
|
|||
decorFitsSystemWindows = false,
|
||||
),
|
||||
content = {
|
||||
val activityWindow = getActivityWindow()
|
||||
val dialogWindow = getDialogWindow()
|
||||
val parentView = LocalView.current.parent as View
|
||||
SideEffect {
|
||||
if (activityWindow != null && dialogWindow != null) {
|
||||
val attributes = WindowManager.LayoutParams().apply {
|
||||
copyFrom(activityWindow.attributes)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
} else {
|
||||
flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
}
|
||||
type = dialogWindow.attributes.type
|
||||
}
|
||||
|
||||
dialogWindow.attributes = attributes
|
||||
parentView.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
activityWindow.decorView.width,
|
||||
activityWindow.decorView.height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
val systemUiController = rememberSystemUiController(getActivityWindow())
|
||||
val dialogSystemUiController = rememberSystemUiController(getDialogWindow())
|
||||
|
||||
ProvideSystemBarsIconsController {
|
||||
val activityWindow = getActivityWindow()
|
||||
val dialogWindow = getDialogWindow()
|
||||
val parentView = LocalView.current.parent as View
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
dialogSystemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
if (activityWindow != null && dialogWindow != null) {
|
||||
val attributes = WindowManager.LayoutParams().apply {
|
||||
copyFrom(activityWindow.attributes)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
} else {
|
||||
flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
}
|
||||
type = dialogWindow.attributes.type
|
||||
}
|
||||
|
||||
dialogWindow.attributes = attributes
|
||||
parentView.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
activityWindow.decorView.width,
|
||||
activityWindow.decorView.height,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SystemBarsIconsDisposable(darkIcons = LocalIsInDarkTheme.current.not())
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
val systemUiController = rememberSystemUiController(getActivityWindow())
|
||||
val dialogSystemUiController = rememberSystemUiController(getDialogWindow())
|
||||
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) {
|
||||
content()
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
dialogSystemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
}
|
||||
}
|
||||
|
||||
SystemBarsIconsDisposable(darkIcons = LocalIsInDarkTheme.current.not())
|
||||
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -308,13 +308,14 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun DialogButtons(
|
||||
confirmButton: DialogButtonUM,
|
||||
dismissButton: DialogButtonUM?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens.spacing4,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ data class MessageBottomSheetUMV2(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class IconImage(@DrawableRes internal var res: Int) : Element
|
||||
|
||||
@Immutable
|
||||
data class Chip(
|
||||
internal var text: TextReference,
|
||||
|
|
@ -54,6 +57,7 @@ data class MessageBottomSheetUMV2(
|
|||
@Immutable
|
||||
data class InfoBlock(
|
||||
internal var icon: Icon? = null,
|
||||
internal var iconImage: IconImage? = null,
|
||||
internal var chip: Chip? = null,
|
||||
var title: TextReference? = null,
|
||||
var body: TextReference? = null,
|
||||
|
|
@ -106,6 +110,10 @@ fun MessageBottomSheetUMV2.InfoBlock.icon(@DrawableRes res: Int, init: MessageBo
|
|||
icon = MessageBottomSheetUMV2.Icon(res).apply(init)
|
||||
}
|
||||
|
||||
fun MessageBottomSheetUMV2.InfoBlock.iconImage(@DrawableRes res: Int) = apply {
|
||||
iconImage = MessageBottomSheetUMV2.IconImage(res)
|
||||
}
|
||||
|
||||
fun MessageBottomSheetUMV2.InfoBlock.chip(text: TextReference, init: MessageBottomSheetUMV2.Chip.() -> Unit = {}) =
|
||||
apply {
|
||||
chip = MessageBottomSheetUMV2.Chip(text).apply(init)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.message
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -25,6 +25,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.icons.HighlightedIcon
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -91,9 +92,7 @@ fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifie
|
|||
@Composable
|
||||
private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
state.icon?.let {
|
||||
BottomSheetIcon(it)
|
||||
}
|
||||
BottomSheetIconContainer(state.icon, state.iconImage)
|
||||
state.title?.let { title ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -125,6 +124,26 @@ private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier:
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CanBeNonNullable")
|
||||
@Composable
|
||||
private fun BottomSheetIconContainer(
|
||||
icon: MessageBottomSheetUMV2.Icon?,
|
||||
iconImage: MessageBottomSheetUMV2.IconImage?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (icon != null) {
|
||||
BottomSheetIcon(icon, modifier)
|
||||
} else if (iconImage != null) {
|
||||
Image(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size56)
|
||||
.clip(CircleShape),
|
||||
painter = painterResource(id = iconImage.res),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomSheetIcon(icon: MessageBottomSheetUMV2.Icon, modifier: Modifier = Modifier) {
|
||||
val tint = when (icon.type) {
|
||||
|
|
@ -144,20 +163,11 @@ private fun BottomSheetIcon(icon: MessageBottomSheetUMV2.Icon, modifier: Modifie
|
|||
MessageBottomSheetUMV2.Icon.BackgroundType.Warning -> TangemTheme.colors.icon.warning
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size56)
|
||||
.clip(CircleShape)
|
||||
.background(backgroundColor.copy(alpha = 0.1F)),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
painter = painterResource(icon.res),
|
||||
contentDescription = null,
|
||||
tint = tint,
|
||||
)
|
||||
},
|
||||
HighlightedIcon(
|
||||
modifier = modifier,
|
||||
icon = icon.res,
|
||||
iconTint = tint,
|
||||
backgroundColor = backgroundColor,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -248,4 +258,33 @@ private fun Preview() {
|
|||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview2() {
|
||||
TangemThemePreview {
|
||||
MessageBottomSheetV2(
|
||||
messageBottomSheetUM {
|
||||
infoBlock {
|
||||
iconImage = MessageBottomSheetUMV2.IconImage(R.drawable.img_visa_notification)
|
||||
title = TextReference.Str("Title Title Title")
|
||||
body = TextReference.Str("Body")
|
||||
chip(text = TextReference.Str("Some chip information"))
|
||||
}
|
||||
primaryButton {
|
||||
text = TextReference.Str("Test")
|
||||
icon = R.drawable.ic_tangem_24
|
||||
}
|
||||
secondaryButton {
|
||||
icon = R.drawable.ic_tangem_24
|
||||
text = TextReference.Str("asdasd")
|
||||
onClick {
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.state
|
||||
|
||||
enum class BottomSheetState {
|
||||
EXPANDED,
|
||||
COLLAPSED,
|
||||
}
|
||||
|
|
@ -39,8 +39,8 @@ fun PinTextField(
|
|||
pinTextColor: PinTextColor,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val textFieldValue = remember(value) {
|
||||
TextFieldValue(value, selection = TextRange(value.length))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.core.ui.components.icons
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun HighlightedIcon(
|
||||
@DrawableRes icon: Int,
|
||||
iconTint: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = iconTint,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size56)
|
||||
.clip(CircleShape)
|
||||
.background(backgroundColor.copy(alpha = 0.1F)),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
painter = painterResource(icon),
|
||||
contentDescription = null,
|
||||
tint = iconTint,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
|
|
@ -18,10 +19,16 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelSize
|
||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -37,6 +44,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
*
|
||||
* @see <a href="https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=4480-1459&t=2QTpi1G7FeTexTFS-4">Figma</a>
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
|
|
@ -63,12 +71,28 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
},
|
||||
)
|
||||
|
||||
AnimatedContent(targetState = state.text) { text ->
|
||||
val horizontalArrangementSize = remember {
|
||||
when (state.size) {
|
||||
LabelSize.REGULAR -> 4.dp
|
||||
LabelSize.BIG -> 8.dp
|
||||
}
|
||||
}
|
||||
|
||||
val paddings = remember {
|
||||
when (state.size) {
|
||||
LabelSize.REGULAR -> PaddingValues(horizontal = 8.dp, vertical = 4.dp)
|
||||
LabelSize.BIG -> PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
modifier = modifier,
|
||||
targetState = state.text,
|
||||
) { text ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(horizontalArrangementSize),
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.background(color = backgroundColor)
|
||||
.then(
|
||||
|
|
@ -82,8 +106,34 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
Modifier
|
||||
},
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
.padding(paddings),
|
||||
) {
|
||||
state.leadingContent.let { leadingContentUM ->
|
||||
when (leadingContentUM) {
|
||||
is LabelLeadingContentUM.Token -> {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.size(16.dp),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(leadingContentUM.iconUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = { CircleShimmer() },
|
||||
error = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.tertiary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
LabelLeadingContentUM.None -> Unit
|
||||
}
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.weight(1.0f, fill = false),
|
||||
text = text.resolveReference(),
|
||||
|
|
@ -109,6 +159,8 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -118,47 +170,93 @@ private fun LabelPreview() {
|
|||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str(
|
||||
"Regular long long long long long long long long long long long long Label",
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
style = LabelStyle.REGULAR,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
leadingContent = LabelLeadingContentUM.Token(
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/euro-coin.png",
|
||||
),
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
),
|
||||
)
|
||||
}
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str(
|
||||
"Regular long long long long long long long long long long long long Label",
|
||||
),
|
||||
style = LabelStyle.REGULAR,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
size = LabelSize.BIG,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
size = LabelSize.BIG,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
size = LabelSize.BIG,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,29 @@
|
|||
package com.tangem.core.ui.components.label.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class LabelUM(
|
||||
val text: TextReference,
|
||||
val style: LabelStyle,
|
||||
val style: LabelStyle = LabelStyle.REGULAR,
|
||||
val size: LabelSize = LabelSize.REGULAR,
|
||||
val leadingContent: LabelLeadingContentUM = LabelLeadingContentUM.None,
|
||||
@DrawableRes val icon: Int? = null,
|
||||
val onIconClick: (() -> Unit)? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed class LabelLeadingContentUM {
|
||||
data object None : LabelLeadingContentUM()
|
||||
data class Token(val iconUrl: String) : LabelLeadingContentUM()
|
||||
}
|
||||
|
||||
enum class LabelStyle {
|
||||
REGULAR, ACCENT, WARNING,
|
||||
}
|
||||
|
||||
enum class LabelSize {
|
||||
REGULAR, BIG,
|
||||
}
|
||||
|
|
@ -296,6 +296,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
showProgress = config.shouldShowProgress,
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
|
|
@ -304,6 +305,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
showProgress = config.shouldShowProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ data class NotificationConfig(
|
|||
val additionalText: TextReference? = null,
|
||||
@DrawableRes val iconResId: Int? = null,
|
||||
val onClick: () -> Unit,
|
||||
val shouldShowProgress: Boolean = false,
|
||||
) : ButtonsState()
|
||||
|
||||
data class SecondaryButtonConfig(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.core.ui.components.pager
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Horizontal pager indicator
|
||||
*
|
||||
* @param pagerState state of pager
|
||||
* @param indicatorCount counter of visible indicator items
|
||||
*/
|
||||
@Composable
|
||||
fun PagerIndicator(pagerState: PagerState, modifier: Modifier = Modifier, indicatorCount: Int = 5) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val indicatorColor = TangemTheme.colors.control.key
|
||||
val overlayColor = TangemTheme.colors.overlay.secondary
|
||||
val indicatorSize = 8.dp
|
||||
val spacing = 4.dp
|
||||
|
||||
val totalWidth: Dp = indicatorSize * indicatorCount + spacing * (indicatorCount - 1)
|
||||
val widthInPx = LocalDensity.current.run { indicatorSize.toPx() }
|
||||
|
||||
val currentItem by remember {
|
||||
derivedStateOf {
|
||||
pagerState.currentPage
|
||||
}
|
||||
}
|
||||
|
||||
val itemCount = pagerState.pageCount
|
||||
|
||||
LaunchedEffect(key1 = currentItem) {
|
||||
val viewportSize = listState.layoutInfo.viewportSize
|
||||
listState.animateScrollToItem(
|
||||
currentItem,
|
||||
(widthInPx / 2 - viewportSize.width / 2).toInt(),
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = overlayColor,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.width(totalWidth),
|
||||
state = listState,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
userScrollEnabled = false,
|
||||
) {
|
||||
indicatorItems(
|
||||
itemCount = itemCount,
|
||||
currentItem = currentItem,
|
||||
indicatorShape = CircleShape,
|
||||
activeColor = indicatorColor,
|
||||
inActiveColor = indicatorColor.copy(alpha = 0.5f),
|
||||
indicatorSize = indicatorSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun LazyListScope.indicatorItems(
|
||||
itemCount: Int,
|
||||
currentItem: Int,
|
||||
indicatorShape: Shape,
|
||||
activeColor: Color,
|
||||
inActiveColor: Color,
|
||||
indicatorSize: Dp,
|
||||
) {
|
||||
items(itemCount) { index ->
|
||||
|
||||
val isSelected = index == currentItem
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(indicatorShape)
|
||||
.size(indicatorSize)
|
||||
.background(
|
||||
if (isSelected) activeColor else inActiveColor,
|
||||
indicatorShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PagerIndicatorPreviewFirstPage() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
pageCount = { 10 },
|
||||
)
|
||||
PagerIndicator(pagerState = pagerState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,7 +246,8 @@ data class EventMessageAction(
|
|||
*
|
||||
* @param onClick The action to perform when the button is clicked. By default, it dismisses the message.
|
||||
* */
|
||||
fun cancelAction(onClick: () -> Unit = onDismissRequest) = EventMessageAction(
|
||||
fun cancelAction(isWarning: Boolean = false, onClick: () -> Unit = onDismissRequest) = EventMessageAction(
|
||||
isWarning = isWarning,
|
||||
title = resourceReference(id = R.string.common_cancel),
|
||||
onClick = onClick,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,30 @@ object Dialogs {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hot wallet creation not supported dialog
|
||||
*
|
||||
* @param leastSupportedVersion least supported OS version name ex. "Android 10"
|
||||
* @param onDismiss lambda be invoked when dialog is dismissed
|
||||
*/
|
||||
fun hotWalletCreationNotSupportedDialog(leastSupportedVersion: String, onDismiss: () -> Unit = {}): DialogMessage {
|
||||
return DialogMessage(
|
||||
title = resourceReference(
|
||||
id = R.string.mobile_wallet_requires_min_os_warning_title,
|
||||
formatArgs = wrappedList(leastSupportedVersion),
|
||||
),
|
||||
message = resourceReference(
|
||||
id = R.string.mobile_wallet_requires_min_os_warning_body,
|
||||
formatArgs = wrappedList(leastSupportedVersion),
|
||||
),
|
||||
firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.common_got_it),
|
||||
onClick = {},
|
||||
),
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal error dialog
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ package com.tangem.core.ui.test
|
|||
object DetailsScreenTestTags {
|
||||
const val SCREEN_CONTAINER = "DETAILS_SCREEN_CONTAINER"
|
||||
const val SCREEN_ITEM = "DETAILS_SCREEN_ITEM"
|
||||
const val VERSION_NAME = "DETAILS_SCREEN_VERSION_NAME"
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ object SendScreenTestTags {
|
|||
|
||||
const val AMOUNT_CONTAINER_TITLE = "SEND_SCREEN_AMOUNT_CONTAINER_TITLE"
|
||||
const val INPUT_TEXT_FIELD = "SEND_SCREEN_INPUT_TEXT_FIELD"
|
||||
const val AMOUNT_ERROR_TEXT = "SEND_SCREEN_AMOUNT_ERROR_TEXT"
|
||||
const val EQUIVALENT_INPUT_AMOUNT = "SEND_SCREEN_EQUIVALENT_INPUT_AMOUNT"
|
||||
const val EXCHANGE_ICON = "SEND_SCREEN_EXCHANGE_ICON"
|
||||
const val TOKEN_NAME = "SEND_SCREEN_TOKEN_NAME"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters.dateDDMMYYYY
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters.dateMMMdd
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters.dateTimeFormatter
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters.dateYYYY
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import org.joda.time.format.DateTimeFormatter
|
||||
import org.joda.time.format.DateTimeFormatterBuilder
|
||||
|
|
@ -80,6 +85,13 @@ object DateTimeFormatters {
|
|||
getBestFormatterBySkeleton("yyyy")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "June 31"
|
||||
*/
|
||||
val dateDMMM: DateTimeFormatter by lazy {
|
||||
getBestFormatterBySkeleton("d MMMM")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "31.06.2020 12:00", "06/31/2020 12:00", "06/31/2020 12:00 PM"
|
||||
*/
|
||||
|
|
@ -87,6 +99,23 @@ object DateTimeFormatters {
|
|||
getBestFormatterBySkeleton("dd.MM.yyyy HH:mm")
|
||||
}
|
||||
|
||||
/**
|
||||
* Local full date formatter (e.g., "dd MMMM, HH:mm")
|
||||
*/
|
||||
val localFullDate: DateTimeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(2)
|
||||
.appendLiteral(' ')
|
||||
.appendMonthOfYearText()
|
||||
.appendLiteral(", ")
|
||||
.appendHourOfDay(2)
|
||||
.appendLiteral(':')
|
||||
.appendMinuteOfHour(2)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
.withZone(DateTimeZone.getDefault())
|
||||
}
|
||||
|
||||
fun formatDate(date: DateTime, formatter: DateTimeFormatter = dateFormatter): String {
|
||||
return formatter.print(date)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,4 +37,51 @@ fun Long.toTimeFormat(formatter: DateTimeFormatter = DateTimeFormatters.timeForm
|
|||
*/
|
||||
fun Long.formatAsDateTime(formatter: DateTimeFormatter): String {
|
||||
return DateTimeFormatters.formatDate(date = DateTime(this, DateTimeZone.getDefault()), formatter = formatter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an ISO 8601 date string and compares it to the current UTC time.
|
||||
*
|
||||
|
||||
* @param now The current date to compare against.
|
||||
* @return A [FormattedDate] subclass.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun getFormattedDate(createdAt: String, now: DateTime): FormattedDate {
|
||||
val pastDateUtc = try {
|
||||
DateTime.parse(createdAt)
|
||||
} catch (_: Exception) {
|
||||
return FormattedDate.FullDate(createdAt)
|
||||
}
|
||||
|
||||
val pastDateLocal = pastDateUtc.withZone(DateTimeZone.getDefault())
|
||||
val isToday = pastDateLocal.isToday()
|
||||
|
||||
val diffInMillis = now.millis - pastDateUtc.millis
|
||||
val diffInMinutes = diffInMillis / (1000 * 60)
|
||||
val diffInHours = diffInMillis / (1000 * 60 * 60)
|
||||
|
||||
return when {
|
||||
diffInMinutes < 1 -> FormattedDate.MinutesAgo(1)
|
||||
diffInMinutes < 60 -> FormattedDate.MinutesAgo(diffInMinutes.toInt())
|
||||
diffInHours < 12 && isToday -> FormattedDate.HoursAgo(diffInHours.toInt())
|
||||
isToday -> {
|
||||
val timeString = DateTimeFormatters.timeFormatter.print(pastDateLocal)
|
||||
FormattedDate.Today(timeString)
|
||||
}
|
||||
else -> {
|
||||
val dateString = DateTimeFormatters.localFullDate.print(pastDateLocal)
|
||||
FormattedDate.FullDate(dateString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Representing different formatted date representations.
|
||||
*/
|
||||
sealed class FormattedDate {
|
||||
data class MinutesAgo(val minutes: Int) : FormattedDate()
|
||||
data class HoursAgo(val hours: Int) : FormattedDate()
|
||||
data class Today(val time: String) : FormattedDate()
|
||||
data class FullDate(val date: String) : FormattedDate()
|
||||
}
|
||||
13
core/ui/src/main/res/drawable/ic_analytics_up_mini_24.xml
Normal file
13
core/ui/src/main/res/drawable/ic_analytics_up_mini_24.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="40" android:viewportWidth="40" android:width="24dp">
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M15.849,26.896C16.954,26.896 17.849,27.728 17.85,28.753V33.615C17.849,34.641 16.954,35.473 15.849,35.473C14.745,35.473 13.85,34.641 13.849,33.615V28.753C13.85,27.727 14.745,26.896 15.849,26.896Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M32.454,20.958C33.558,20.958 34.454,21.819 34.454,22.879V33.552C34.454,34.612 33.558,35.473 32.454,35.473C31.35,35.473 30.454,34.612 30.454,33.552V22.879C30.454,21.818 31.349,20.958 32.454,20.958Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M7.547,29.467C8.651,29.467 9.547,30.362 9.547,31.467V33.472C9.547,34.576 8.651,35.472 7.547,35.472C6.442,35.472 5.547,34.576 5.547,33.472V31.467C5.547,30.362 6.442,29.467 7.547,29.467Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M24.151,23.976C25.256,23.976 26.151,24.755 26.151,25.716V33.732C26.151,34.693 25.256,35.472 24.151,35.472C23.047,35.472 22.151,34.693 22.151,33.732V25.716C22.152,24.755 23.047,23.976 24.151,23.976Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M29.46,4.062C29.927,4.036 30.392,4.174 30.77,4.457C31.203,4.781 31.486,5.266 31.556,5.801L32.671,14.396C32.813,15.491 32.04,16.495 30.944,16.637C29.849,16.779 28.846,16.006 28.704,14.911L28.228,11.251C26.87,12.973 25.156,14.988 23.389,16.644C19.165,20.601 14.718,22.22 9.224,21.976C8.12,21.927 7.265,20.992 7.314,19.889C7.363,18.786 8.298,17.931 9.401,17.98C13.802,18.175 17.202,16.958 20.653,13.725C22.214,12.263 23.781,10.43 25.071,8.796L20.395,9.536C19.305,9.709 18.28,8.965 18.107,7.874C17.935,6.784 18.679,5.759 19.769,5.586L29.259,4.083L29.46,4.062Z"/>
|
||||
|
||||
</vector>
|
||||
12
core/ui/src/main/res/drawable/ic_explore_16.xml
Normal file
12
core/ui/src/main/res/drawable/ic_explore_16.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M2.667,8C2.667,5.055 5.055,2.667 8,2.667C10.946,2.667 13.333,5.055 13.333,8C13.333,10.946 10.946,13.333 8,13.333C5.055,13.333 2.667,10.946 2.667,8ZM1.333,8C1.333,11.682 4.318,14.667 8,14.667C11.682,14.667 14.667,11.682 14.667,8C14.667,4.318 11.682,1.334 8,1.334C4.318,1.334 1.333,4.318 1.333,8Z"
|
||||
android:fillColor="#919191"/>
|
||||
<path
|
||||
android:pathData="M5.032,11.297C4.984,11.287 4.94,11.263 4.906,11.229C4.871,11.194 4.847,11.15 4.837,11.102C4.827,11.054 4.832,11.005 4.85,10.959L6.315,7.297C6.416,7.045 6.567,6.816 6.758,6.625C6.95,6.433 7.178,6.282 7.429,6.182L11.092,4.717C11.137,4.699 11.188,4.695 11.235,4.704C11.283,4.714 11.328,4.738 11.362,4.772C11.396,4.807 11.42,4.851 11.43,4.899C11.44,4.947 11.435,4.997 11.417,5.042L9.952,8.704C9.851,8.956 9.7,9.185 9.509,9.376C9.317,9.568 9.089,9.718 8.838,9.819L5.175,11.284C5.13,11.302 5.08,11.306 5.032,11.297ZM8.28,8.736C8.425,8.707 8.559,8.636 8.664,8.531C8.768,8.426 8.84,8.292 8.869,8.147C8.898,8.001 8.884,7.85 8.827,7.713C8.77,7.576 8.673,7.46 8.55,7.377C8.427,7.295 8.282,7.251 8.134,7.251C7.935,7.251 7.744,7.33 7.603,7.47C7.463,7.611 7.384,7.802 7.384,8.001C7.384,8.149 7.427,8.294 7.51,8.417C7.592,8.54 7.709,8.637 7.846,8.694C7.983,8.75 8.134,8.765 8.28,8.736Z"
|
||||
android:fillColor="#919191"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_heart_20.xml
Normal file
15
core/ui/src/main/res/drawable/ic_heart_20.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M13.75,1.75C16.73,1.75 19.083,4.093 19.084,7.083C19.084,8.9 18.257,10.495 16.961,12.081C15.673,13.657 13.843,15.315 11.713,17.247L10.505,18.347L10,18.806L9.495,18.347L8.288,17.247C6.157,15.315 4.327,13.657 3.039,12.081C1.742,10.495 0.917,8.9 0.917,7.083C0.917,4.093 3.27,1.75 6.25,1.75C7.646,1.75 8.986,2.29 10,3.174C11.013,2.29 12.353,1.75 13.75,1.75Z"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#1E1E1E"/>
|
||||
</group>
|
||||
</vector>
|
||||
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