Updated on 2026-08-14
This commit is contained in:
commit
44837422c7
94 changed files with 3076 additions and 29 deletions
|
|
@ -4,6 +4,7 @@ plugins {
|
|||
kotlin("kapt")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -95,6 +96,7 @@ android {
|
|||
resources.excludes += "lib/x86_64/darwin/libscrypt.dylib"
|
||||
resources.excludes += "lib/x86_64/freebsd/libscrypt.so"
|
||||
resources.excludes += "lib/x86_64/linux/libscrypt.so"
|
||||
resources.excludes += "META-INF/gradle/incremental.annotation.processors"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +109,13 @@ dependencies {
|
|||
implementation(project(":domain"))
|
||||
implementation(project(":network"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":libs:crypto"))
|
||||
|
||||
/** Features */
|
||||
implementation(project(":features:referral:presentation"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
implementation(project(":features:referral:data"))
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(AndroidX.coreKtx)
|
||||
|
|
@ -134,9 +143,17 @@ dependencies {
|
|||
implementation(Firebase.firebaseCrashlytics)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(Tangem.blockchain)
|
||||
implementation(Tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(Tangem.cardCore)
|
||||
implementation(Tangem.cardAndroid)
|
||||
implementation(Tangem.cardAndroid) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.materialComponent)
|
||||
|
|
@ -169,7 +186,7 @@ dependencies {
|
|||
}
|
||||
implementation(Library.accompanistAppCompatTheme)
|
||||
implementation(Library.accompanistSystemUiController)
|
||||
implementation(Library.skydovesVeil)
|
||||
implementation(Library.xmlShimmer)
|
||||
implementation(Library.viewBindingDelegate)
|
||||
implementation(Library.armadillo)
|
||||
implementation(Library.googlePlayServicesWallet)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.tap.domain.TangemSdkManager
|
|||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -49,6 +50,7 @@ private val mainCoroutineContext: CoroutineContext
|
|||
get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope")
|
||||
val mainScope = CoroutineScope(mainCoroutineContext)
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder {
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import com.tangem.tap.persistence.CardBalanceStateAdapter
|
|||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.zendesk.logger.Logger
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
import zendesk.chat.Chat
|
||||
|
|
@ -60,6 +61,7 @@ lateinit var shopService: TangemShopService
|
|||
lateinit var assetReader: AssetReader
|
||||
lateinit var userTokensRepository: UserTokensRepository
|
||||
|
||||
@HiltAndroidApp
|
||||
class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ import com.tangem.wallet.R
|
|||
object TangemTypography {
|
||||
|
||||
val body1 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto)),
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 16.0.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
lineHeight = 24.0.sp,
|
||||
)
|
||||
val body2 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto)),
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 14.0.sp,
|
||||
letterSpacing = 0.25.sp,
|
||||
lineHeight = 20.0.sp,
|
||||
|
|
@ -27,13 +27,13 @@ object TangemTypography {
|
|||
lineHeight = 20.0.sp,
|
||||
)
|
||||
val caption = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto)),
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 12.0.sp,
|
||||
letterSpacing = 0.4000000059604645.sp,
|
||||
lineHeight = 16.0.sp,
|
||||
)
|
||||
val headline1 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto)),
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 34.0.sp,
|
||||
letterSpacing = 0.0.sp,
|
||||
lineHeight = 44.0.sp,
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun deriveMissingBlockchains(
|
||||
fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<Currency>,
|
||||
onSuccess: (ScanResponse) -> Unit,
|
||||
|
|
|
|||
26
app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
Normal file
26
app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.features.tokens.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import org.rekotlin.Store
|
||||
|
||||
/**
|
||||
* Holds objects from old modules, that missing in DI graph
|
||||
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI
|
||||
*/
|
||||
class AppStateHolder {
|
||||
|
||||
val scanResponse: ScanResponse? = null
|
||||
val walletState: WalletState? = null
|
||||
val userTokensRepository: UserTokensRepository? = null
|
||||
val mainStore: Store<AppState>? = null
|
||||
val tokesMiddleware: TokensMiddleware? = null
|
||||
|
||||
fun getActualCard(): Card? {
|
||||
return scanResponse?.card
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.NonNativeToken
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class DerivationManagerImpl(
|
||||
private val appStateHolder: AppStateHolder,
|
||||
) : DerivationManager {
|
||||
|
||||
override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine<Boolean> { continuation ->
|
||||
val tokesMiddleware = requireNotNull(appStateHolder.tokesMiddleware) { "tokesMiddleware is null" }
|
||||
val blockchain = Blockchain.fromNetworkId(currency.networkId)
|
||||
val card = appStateHolder.getActualCard()
|
||||
if (blockchain != null && card != null) {
|
||||
val appToken = if (currency is NonNativeToken) {
|
||||
Token(
|
||||
symbol = currency.symbol,
|
||||
contractAddress = currency.contractAddress,
|
||||
decimals = currency.decimalCount,
|
||||
)
|
||||
} else null
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, card)
|
||||
val appCurrency = com.tangem.tap.features.wallet.models.Currency.fromBlockchainNetwork(
|
||||
blockchainNetwork,
|
||||
appToken,
|
||||
)
|
||||
if (appStateHolder.scanResponse != null) {
|
||||
tokesMiddleware.deriveMissingBlockchains(
|
||||
scanResponse = appStateHolder.scanResponse,
|
||||
listOf(appCurrency),
|
||||
) {
|
||||
continuation.resumeWith(Result.success(true))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continuation.resumeWith(Result.failure(IllegalStateException("no blockchain or card found")))
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasDerivation(networkId: String): Boolean {
|
||||
val scanResponse = appStateHolder.scanResponse
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
if (scanResponse != null && blockchain != null) {
|
||||
val derivationPath = blockchain.derivationPath(appStateHolder.getActualCard()?.derivationStyle)?.rawPath
|
||||
if (derivationPath.isNullOrEmpty()) return false
|
||||
return scanResponse.hasDerivation(
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
152
app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt
Normal file
152
app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.NativeToken
|
||||
import com.tangem.lib.crypto.models.NonNativeToken
|
||||
import com.tangem.tap.domain.extensions.getUserWalletId
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
class UserWalletManagerImpl(
|
||||
private val appStateHolder: AppStateHolder,
|
||||
private val walletManagerFactory: WalletManagerFactory,
|
||||
) : UserWalletManager {
|
||||
|
||||
override suspend fun getUserTokens(): List<Currency> {
|
||||
val card = appStateHolder.getActualCard()
|
||||
val userTokensRepository =
|
||||
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
|
||||
if (card != null) {
|
||||
userTokensRepository.getUserTokens(card)
|
||||
.filter { it.isToken() }
|
||||
.map {
|
||||
if (it is com.tangem.tap.features.wallet.models.Currency.Token) {
|
||||
NonNativeToken(
|
||||
id = it.coinId ?: "",
|
||||
name = it.currencyName,
|
||||
symbol = it.currencySymbol,
|
||||
networkId = it.blockchain.toNetworkId(),
|
||||
contractAddress = it.token.contractAddress,
|
||||
decimalCount = it.token.decimals,
|
||||
)
|
||||
} else {
|
||||
NativeToken(
|
||||
id = it.coinId ?: "",
|
||||
name = it.currencyName,
|
||||
symbol = it.currencySymbol,
|
||||
networkId = it.blockchain.toNetworkId(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun getWalletId(): String {
|
||||
return appStateHolder.getActualCard()?.getUserWalletId() ?: ""
|
||||
}
|
||||
|
||||
override suspend fun isTokenAdded(currency: Currency): Boolean {
|
||||
val card = appStateHolder.getActualCard()
|
||||
val userTokensRepository =
|
||||
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
|
||||
if (card != null) {
|
||||
return userTokensRepository.getUserTokens(card)
|
||||
.any { it.coinId.equals(currency.id) } //todo ensure that its the same ids
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//todo check is it sync operation
|
||||
override fun addToken(currency: Currency) {
|
||||
val action = if (currency is NativeToken) {
|
||||
val card = appStateHolder.getActualCard()
|
||||
if (card != null) {
|
||||
addNativeTokenToWalletAction(currency, card)
|
||||
} else {
|
||||
throw IllegalStateException("card not found")
|
||||
}
|
||||
} else {
|
||||
addNonNativeTokenToWalletAction(currency as NonNativeToken)
|
||||
}
|
||||
val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" }
|
||||
mainStore.dispatch(action)
|
||||
}
|
||||
|
||||
override fun getWalletAddress(currency: Currency): String {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.networkId)
|
||||
val card = appStateHolder.getActualCard()
|
||||
if (blockchain != null && card != null) {
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, card)
|
||||
val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
|
||||
if (walletManager != null) {
|
||||
return walletManager.wallet.address
|
||||
} else {
|
||||
throw IllegalStateException("no wallet manager found")
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("no blockchain or card found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNativeTokenToWalletAction(token: NativeToken, card: Card): Action {
|
||||
val blockchain = Blockchain.fromNetworkId(token.networkId)
|
||||
val scanResponse = appStateHolder.scanResponse
|
||||
if (blockchain != null && scanResponse != null) {
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, card)
|
||||
var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
|
||||
if (walletManager == null) {
|
||||
walletManager = walletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchain = blockchain,
|
||||
derivationParams = createDerivationParams(card.derivationStyle),
|
||||
)
|
||||
}
|
||||
return WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchain = blockchainNetwork,
|
||||
walletManager = walletManager,
|
||||
save = true,
|
||||
)
|
||||
} else {
|
||||
throw IllegalStateException("blockchain is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNonNativeTokenToWalletAction(token: NonNativeToken): Action {
|
||||
val card = appStateHolder.getActualCard()
|
||||
val blockchain = Blockchain.fromNetworkId(token.networkId)
|
||||
if (card != null && blockchain != null) {
|
||||
return WalletAction.MultiWallet.AddToken(
|
||||
token = com.tangem.blockchain.common.Token(
|
||||
id = token.id,
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
contractAddress = token.contractAddress,
|
||||
decimals = token.decimalCount,
|
||||
),
|
||||
blockchain = BlockchainNetwork(
|
||||
blockchain,
|
||||
card,
|
||||
),
|
||||
save = true,
|
||||
)
|
||||
} else {
|
||||
throw IllegalStateException("no card or blockchain found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? {
|
||||
return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom
|
||||
}
|
||||
}
|
||||
41
app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt
Normal file
41
app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.proxy.di
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.DerivationManagerImpl
|
||||
import com.tangem.tap.proxy.UserWalletManagerImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class ProxyModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppStateHolder(): AppStateHolder {
|
||||
return AppStateHolder()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletManager(appStateHolder: AppStateHolder): UserWalletManager {
|
||||
return UserWalletManagerImpl(
|
||||
appStateHolder = appStateHolder,
|
||||
walletManagerFactory = WalletManagerFactory(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDerivationManager(appStateHolder: AppStateHolder): DerivationManager {
|
||||
return DerivationManagerImpl(
|
||||
appStateHolder = appStateHolder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,8C13.1,8 14,7.1 14,6C14,4.9 13.1,4 12,4C10.9,4 10,4.9 10,6C10,7.1 10.9,8 12,8ZM12,10C10.9,10 10,10.9 10,12C10,13.1 10.9,14 12,14C13.1,14 14,13.1 14,12C14,10.9 13.1,10 12,10ZM12,16C10.9,16 10,16.9 10,18C10,19.1 10.9,20 12,20C13.1,20 14,19.1 14,18C14,16.9 13.1,16 12,16Z"
|
||||
android:fillColor="#000"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/details_menu"
|
||||
android:title="@string/details_title"
|
||||
android:icon="@drawable/ic_more_vertical_24"
|
||||
app:iconTint="@color/darkGray6"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:id="@+id/details_menu"
|
||||
android:title="@string/details_title"
|
||||
android:icon="@drawable/ic_more_vertical"
|
||||
app:iconTint="@color/darkGray6"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ buildscript {
|
|||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.google.dagger.hilt.android") version "2.44" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
|
|
|||
|
|
@ -34,12 +34,16 @@ object Library {
|
|||
const val appsflyer = "com.appsflyer:af-android-sdk:" + Versions.appsflyer
|
||||
const val armadillo = "at.favre.lib:armadillo:" + Versions.armadillo
|
||||
const val coil = "io.coil-kt:coil:" + Versions.coil
|
||||
const val composeShimmer = "com.valentinilk.shimmer:compose-shimmer:" + Versions.composeShimmer
|
||||
const val coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:" + Versions.coroutine
|
||||
const val desugarJdkLibs = "com.android.tools:desugar_jdk_libs:" + Versions.desugarJdkLibs
|
||||
const val googlePlayCore = "com.google.android.play:core:" + Versions.googlePlayCore
|
||||
const val googlePlayCoreKtx = "com.google.android.play:core-ktx:" + Versions.googlePlayCoreKtx
|
||||
const val googlePlayServicesWallet =
|
||||
"com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet
|
||||
const val hilt = "com.google.dagger:hilt-android:" + Versions.hilt
|
||||
const val hiltKapt = "com.google.dagger:hilt-compiler:" + Versions.hilt
|
||||
const val jodatime = "net.danlew:android.joda:" + Versions.jodatime
|
||||
const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt
|
||||
const val lottie = "com.airbnb.android:lottie:" + Versions.lottie
|
||||
const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent
|
||||
|
|
@ -49,7 +53,6 @@ object Library {
|
|||
const val okHttpLogging = "com.squareup.okhttp3:logging-interceptor:" + Versions.okhttp
|
||||
const val otaliastudiosCameraView = "com.otaliastudios:cameraview:" + Versions.otaliastudiosCameraView
|
||||
const val shopifyBuySdk = "com.shopify.mobilebuysdk:buy3:" + Versions.shopifyBuySdk
|
||||
const val skydovesVeil = "com.github.skydoves:androidveil:" + Versions.skydovesAndroidVeil
|
||||
const val spongecastleCryptoCore = "com.madgag.spongycastle:core:" + Versions.spongycastleCryptoCore
|
||||
const val reKotlin = "org.rekotlin:rekotlin:" + Versions.rekotlin
|
||||
const val retrofit = "com.squareup.retrofit2:retrofit:" + Versions.retrofit
|
||||
|
|
@ -57,6 +60,7 @@ object Library {
|
|||
const val timber = "com.jakewharton.timber:timber:" + Versions.timber
|
||||
const val viewBindingDelegate =
|
||||
"com.github.kirich1409:viewbindingpropertydelegate-noreflection:" + Versions.viewBindingDelegate
|
||||
const val xmlShimmer = "com.github.skydoves:androidveil:" + Versions.xmlShimmer
|
||||
const val zendeskChat = "com.zendesk:chat:" + Versions.zendeskChat
|
||||
const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging
|
||||
const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ object Versions {
|
|||
const val appsflyer = "6.5.1"
|
||||
const val armadillo = "0.9.0"
|
||||
const val coil = "2.1.0"
|
||||
const val composeShimmer = "1.0.3"
|
||||
const val coroutine = "1.5.2"
|
||||
const val desugarJdkLibs = "1.1.5"
|
||||
const val firebase = "26.0.0"
|
||||
|
|
@ -35,6 +36,8 @@ object Versions {
|
|||
const val googlePlayCore = "1.10.3"
|
||||
const val googlePlayCoreKtx = "1.8.1"
|
||||
const val googlePlayServicesWallet = "19.1.0"
|
||||
const val hilt = "2.44"
|
||||
const val jodatime = "2.12.1"
|
||||
const val kotsonGsonExt = "2.5.0"
|
||||
const val lottie = "3.4.0"
|
||||
const val moshi = "1.12.0"
|
||||
|
|
@ -44,10 +47,10 @@ object Versions {
|
|||
const val retrofit = "2.8.1"
|
||||
const val retrofitMoshiConverter = "2.6.0"
|
||||
const val shopifyBuySdk = "12.0.0"
|
||||
const val skydovesAndroidVeil = "1.1.2"
|
||||
const val spongycastleCryptoCore = "1.58.0.0"
|
||||
const val timber = "4.7.1"
|
||||
const val viewBindingDelegate = "1.5.6"
|
||||
const val xmlShimmer = "1.1.2"
|
||||
const val zendeskChat = "3.3.5"
|
||||
const val zendeskMessaging = "5.2.4"
|
||||
const val zxingQrBarcodeScanner = "1.9.8"
|
||||
|
|
|
|||
1
core/datasource/.gitignore
vendored
Normal file
1
core/datasource/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
48
core/datasource/build.gradle.kts
Normal file
48
core/datasource/build.gradle.kts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
|
||||
/** Logging */
|
||||
implementation(Library.timber)
|
||||
|
||||
/** Network */
|
||||
implementation(Library.retrofit)
|
||||
implementation(Library.retrofitMoshiConverter)
|
||||
implementation(Library.moshi)
|
||||
implementation(Library.moshiKotlin)
|
||||
implementation(Library.okHttp)
|
||||
implementation(Library.okHttpLogging)
|
||||
|
||||
/** Time */
|
||||
implementation(Library.jodatime)
|
||||
}
|
||||
2
core/datasource/src/main/AndroidManifest.xml
Normal file
2
core/datasource/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.datasource" />
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.api.referral
|
||||
|
||||
import com.tangem.datasource.api.referral.models.ReferralResponse
|
||||
import com.tangem.datasource.api.referral.models.StartReferralBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Api for referral feature
|
||||
*/
|
||||
interface
|
||||
ReferralApi {
|
||||
|
||||
/** Returns referral status by [walletId] */
|
||||
@GET("referral/{walletId}")
|
||||
suspend fun getReferralStatus(@Query("name") walletId: String): ReferralResponse
|
||||
|
||||
/** Make user referral, requires [StartReferralBody] */
|
||||
@POST("referral")
|
||||
suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.datasource.api.referral.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import org.joda.time.DateTime
|
||||
|
||||
/**
|
||||
* Main response class for referral API
|
||||
* contains all necessary info about users program status
|
||||
*/
|
||||
data class ReferralResponse(
|
||||
@Json(name = "conditions") val conditions: Conditions,
|
||||
@Json(name = "referral") val referral: Referral?,
|
||||
)
|
||||
|
||||
data class Conditions(
|
||||
@Json(name = "award") val award: Int,
|
||||
@Json(name = "discount") val discount: Discount,
|
||||
@Json(name = "awards") val awards: List<Award>,
|
||||
@Json(name = "tosLink") val tosLink: String,
|
||||
)
|
||||
|
||||
data class Discount(
|
||||
@Json(name = "amount") val amount: Int,
|
||||
@Json(name = "discountType") val discountType: DiscountType,
|
||||
)
|
||||
|
||||
data class Award(
|
||||
@Json(name = "amount") val amount: Int,
|
||||
@Json(name = "token") val token: Token,
|
||||
)
|
||||
|
||||
data class Token(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "contractAddress") val contractAddress: String?,
|
||||
@Json(name = "decimalCount") val decimalCount: Int?,
|
||||
)
|
||||
|
||||
data class Referral(
|
||||
@Json(name = "shareLink") val shareLink: String,
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "promocode") val promocode: String,
|
||||
@Json(name = "walletsPurchased") val walletsPurchased: Int,
|
||||
@Json(name = "termsAcceptedAt") val termsAcceptedAt: DateTime?,
|
||||
)
|
||||
|
||||
enum class DiscountType {
|
||||
PERCENTAGE, VALUE
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.referral.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/** Body for make user referral */
|
||||
data class StartReferralBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "tokenId") val tokenId: String,
|
||||
@Json(name = "address") val address: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.api.referral.ReferralApi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi {
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(
|
||||
MoshiConverterFactory.create(),
|
||||
)
|
||||
.baseUrl(PROD_REFERRAL_BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
.create(ReferralApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PROD_REFERRAL_BASE_URL = ""
|
||||
}
|
||||
}
|
||||
1
core/ui/.gitignore
vendored
Normal file
1
core/ui/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
41
core/ui/build.gradle.kts
Normal file
41
core/ui/build.gradle.kts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = Versions.compose
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Compose */
|
||||
implementation(Compose.foundation)
|
||||
implementation(Compose.material)
|
||||
implementation(Compose.uiTooling)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.accompanistSystemUiController)
|
||||
}
|
||||
2
core/ui/src/main/AndroidManifest.xml
Normal file
2
core/ui/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.core.ui" />
|
||||
183
core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
Normal file
183
core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.ButtonColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.buttonColor
|
||||
import com.tangem.core.ui.res.textColor
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the beginning of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun PrimaryStartIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Text(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the end of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun PrimaryEndIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Text(text = text)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButtonRow(
|
||||
modifier: Modifier,
|
||||
enabled: Boolean,
|
||||
onClicked: () -> Unit,
|
||||
content: @Composable (RowScope.() -> Unit),
|
||||
) {
|
||||
Button(
|
||||
onClick = onClicked,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(R.dimen.size48)),
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY),
|
||||
contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2),
|
||||
disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED),
|
||||
disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED),
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
79
core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt
Normal file
79
core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.textColor
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_SimpleInfoCard_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_SimpleInfoCard_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Small card with text information attached to the edges
|
||||
*
|
||||
* @param startText text information attached to the left edge
|
||||
* @param endText text information attached to the right edge
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=290%3A339&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun SmallInfoCard(startText: String, endText: String) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
color = MaterialTheme.colors.secondary,
|
||||
elevation = dimensionResource(id = R.dimen.elevation2),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(R.dimen.size48))
|
||||
.padding(
|
||||
horizontal = dimensionResource(id = R.dimen.spacing16),
|
||||
vertical = dimensionResource(id = R.dimen.spacing12),
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = startText,
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle2
|
||||
)
|
||||
Text(
|
||||
text = endText,
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
|
||||
@Composable
|
||||
fun VerticalSpacer(@DimenRes spaceResId: Int) {
|
||||
Spacer(modifier = Modifier.height(dimensionResource(id = spaceResId)))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizontalSpacer(@DimenRes spaceResId: Int) {
|
||||
Spacer(modifier = Modifier.width(dimensionResource(id = spaceResId)))
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
package com.tangem.core.ui.components.appbar
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.models.AdditionalButton
|
||||
import com.tangem.core.ui.res.IconColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.iconColor
|
||||
import com.tangem.core.ui.res.textColor
|
||||
|
||||
/**
|
||||
* App bar with title and two additional buttons
|
||||
*
|
||||
* @param startButton button information attached to the left edge
|
||||
* @param endButton button information attached to the right edge
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=50%3A403&t=tRQ4KlzkjV7TCLZl-4">
|
||||
* Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun AppBarWithAdditionalButtons(
|
||||
text: String,
|
||||
startButton: AdditionalButton? = null,
|
||||
endButton: AdditionalButton? = null,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(id = R.dimen.size56))
|
||||
.padding(all = dimensionResource(id = R.dimen.spacing16)),
|
||||
) {
|
||||
if (startButton != null) {
|
||||
IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = startButton.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(dimensionResource(id = R.dimen.size24)),
|
||||
tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
)
|
||||
|
||||
if (endButton != null) {
|
||||
IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = endButton.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(dimensionResource(id = R.dimen.size24)),
|
||||
tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithAdditionalButtons_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
startButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_scan,
|
||||
onIconClicked = {},
|
||||
),
|
||||
endButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_more_vertical,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithAdditionalButtons_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
startButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_scan,
|
||||
onIconClicked = {},
|
||||
),
|
||||
endButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_more_vertical,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithOnlyStartButtons_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
startButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_scan,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
startButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_scan,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithOnlyEndButtons_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
endButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_more_vertical,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AppBarWithOnlyEndButtons_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = "Tangem",
|
||||
endButton = AdditionalButton(
|
||||
iconRes = R.drawable.ic_more_vertical,
|
||||
onIconClicked = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.core.ui.components.appbar
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun PreviewAppBarWithBackButtonInLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
AppBarWithBackButton(text = "Title", onBackClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
|
||||
@Composable
|
||||
fun PreviewAppBarWithBackButtonInDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
AppBarWithBackButton(text = "Title", onBackClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* App bar with back button and optional title
|
||||
*
|
||||
* @param text optional title
|
||||
* @param onBackClick the lambda to be invoked when this icon is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=123%3A287&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(all = dimensionResource(R.dimen.spacing16))
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size = dimensionResource(R.dimen.size24))
|
||||
.clickable { onBackClick() },
|
||||
tint = MaterialTheme.colors.onPrimary,
|
||||
)
|
||||
if (!text.isNullOrBlank()) {
|
||||
Text(
|
||||
text = text,
|
||||
color = MaterialTheme.colors.onPrimary,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.ui.components.appbar.models
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
data class AdditionalButton(
|
||||
@DrawableRes val iconRes: Int,
|
||||
val onIconClicked: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.core.ui.components.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.IconColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.iconColor
|
||||
|
||||
/**
|
||||
* Bottom sheet indicator
|
||||
*
|
||||
* @see <a href="https://www.figma.com/file/17JyRbuUEZ42DluaFEuGQk/Atoms?node-id=135%3A25&t=3dvxYMZBox4jxJc1-4">
|
||||
* Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun BottomSheetIndicator() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(id = R.dimen.size20)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(dimensionResource(id = R.dimen.size32))
|
||||
.height(dimensionResource(id = R.dimen.size4))
|
||||
.background(
|
||||
color = MaterialTheme.colors.iconColor(type = IconColorType.INACTIVE),
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius2)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(heightDp = 20, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_BottomSheetIndicator_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
BottomSheetIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(heightDp = 20, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_BottomSheetIndicator_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
BottomSheetIndicator()
|
||||
}
|
||||
}
|
||||
16
core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt
Normal file
16
core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
enum class ButtonColorType(val lightColor: Color, val darkColor: Color) {
|
||||
PRIMARY(lightColor = Dark6, darkColor = Light1),
|
||||
SECONDARY(lightColor = Light2, darkColor = Dark5),
|
||||
DISABLED(lightColor = Light2, darkColor = Dark6),
|
||||
POSITIVE(lightColor = Meadow, darkColor = Meadow),
|
||||
POSITIVE_DISABLED(lightColor = MagicMint, darkColor = DarkGreen)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Colors.buttonColor(type: ButtonColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor
|
||||
45
core/ui/src/main/java/com/tangem/core/ui/res/Color.kt
Normal file
45
core/ui/src/main/java/com/tangem/core/ui/res/Color.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
// region Base
|
||||
val Black = Color(0xFF000000)
|
||||
val White = Color(0xFFFFFFFF)
|
||||
// endregion Base
|
||||
|
||||
// region Dark
|
||||
val Dark1 = Color(0xFF919191)
|
||||
val Dark2 = Color(0xFF656565)
|
||||
val Dark3 = Color(0xFF494949)
|
||||
val Dark4 = Color(0xFF3B3B3B)
|
||||
val Dark5 = Color(0xFF303030)
|
||||
val Dark6 = Color(0xFF1E1E1E)
|
||||
// endregion Dark
|
||||
|
||||
// region Light
|
||||
val Light1 = Color(0xFFF5F5F5)
|
||||
val Light2 = Color(0xFFEBEBEB)
|
||||
val Light3 = Color(0xFFD3D3D3)
|
||||
val Light4 = Color(0xFFC9C9C9)
|
||||
val Light5 = Color(0xFFB0B0B0)
|
||||
// endregion Light
|
||||
|
||||
// region Green
|
||||
val Meadow = Color(0xFF1ACE80)
|
||||
val MagicMint = Color(0xFFA3EBCC)
|
||||
val DarkGreen = Color(0xFF06311F)
|
||||
// endregion Green
|
||||
|
||||
// region Blue
|
||||
val Cobalt = Color(0xFF0029FF)
|
||||
val Azure = Color(0xFF007AFF)
|
||||
// endregion Blue
|
||||
|
||||
// region Red
|
||||
val Amaranth = Color(0xFFDE1010)
|
||||
val Flamingo = Color(0xFFED7979)
|
||||
// endregion Red
|
||||
|
||||
// region Yellow
|
||||
val Tangerine = Color(0xFFFFB71B)
|
||||
// endregion Yellow
|
||||
19
core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt
Normal file
19
core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
enum class IconColorType(val lightColor: Color, val darkColor: Color) {
|
||||
PRIMARY1(lightColor = Black, darkColor = White),
|
||||
PRIMARY2(lightColor = White, darkColor = Dark6),
|
||||
SECONDARY(lightColor = Dark2, darkColor = Dark1),
|
||||
INFORMATIVE(lightColor = Light5, darkColor = Dark2),
|
||||
INACTIVE(lightColor = Light4, darkColor = Dark4),
|
||||
ACCENT(lightColor = Meadow, darkColor = Meadow),
|
||||
WARNING(lightColor = Amaranth, darkColor = Amaranth),
|
||||
ATTENTION(lightColor = Tangerine, darkColor = Tangerine)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Colors.iconColor(type: IconColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor
|
||||
53
core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
Normal file
53
core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
internal val isSystemInDarkTheme: Boolean = false // TODO: use isSystemInDarkTheme() for automatic color detection
|
||||
|
||||
@Composable
|
||||
fun TangemTheme(
|
||||
isDarkTheme: Boolean = isSystemInDarkTheme,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(color = if (isDarkTheme) Black else Light1, darkIcons = !isDarkTheme)
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colors = if (isDarkTheme) DarkColors else LightColors,
|
||||
typography = TangemTypography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private val LightColors = lightColors(
|
||||
primary = Light1,
|
||||
primaryVariant = Light1,
|
||||
secondary = White,
|
||||
background = Light1,
|
||||
surface = White,
|
||||
error = Amaranth,
|
||||
onPrimary = Dark6,
|
||||
onSecondary = Dark6,
|
||||
onBackground = Dark6,
|
||||
onSurface = Dark6,
|
||||
)
|
||||
|
||||
private val DarkColors = darkColors(
|
||||
primary = Black,
|
||||
primaryVariant = Black,
|
||||
secondary = Dark6,
|
||||
background = Black,
|
||||
surface = Dark6,
|
||||
error = Amaranth,
|
||||
onPrimary = White,
|
||||
onSecondary = White,
|
||||
onBackground = White,
|
||||
onSurface = White,
|
||||
)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.ExperimentalUnitApi
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
private val RobotoFamily = FontFamily(
|
||||
Font(R.font.roboto_regular, FontWeight.Normal),
|
||||
Font(R.font.roboto_medium, FontWeight.Medium),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalUnitApi::class)
|
||||
val TangemTypography = Typography(
|
||||
defaultFontFamily = RobotoFamily,
|
||||
h1 = TextStyle(
|
||||
fontSize = 34.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(0f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(44f, TextUnitType.Sp),
|
||||
),
|
||||
h2 = TextStyle(
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(0.18f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(32f, TextUnitType.Sp),
|
||||
),
|
||||
h3 = TextStyle(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(0.15f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(24f, TextUnitType.Sp),
|
||||
),
|
||||
subtitle1 = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(0.15f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(24f, TextUnitType.Sp),
|
||||
),
|
||||
subtitle2 = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(0.5f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(24f, TextUnitType.Sp),
|
||||
),
|
||||
body1 = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(0.5f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(24f, TextUnitType.Sp),
|
||||
),
|
||||
body2 = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(0.25f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(20f, TextUnitType.Sp),
|
||||
),
|
||||
button = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(0.1f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(16f, TextUnitType.Sp),
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(0.4f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(16f, TextUnitType.Sp),
|
||||
),
|
||||
overline = TextStyle(
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(1.5f, TextUnitType.Sp),
|
||||
lineHeight = TextUnit(16f, TextUnitType.Sp),
|
||||
),
|
||||
)
|
||||
20
core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt
Normal file
20
core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
enum class TextColorType(val lightColor: Color, val darkColor: Color) {
|
||||
PRIMARY1(lightColor = Dark6, darkColor = White),
|
||||
PRIMARY2(lightColor = White, darkColor = Dark6),
|
||||
SECONDARY(lightColor = Dark2, darkColor = Light5),
|
||||
TERTIARY(lightColor = Dark1, darkColor = Dark1),
|
||||
DISABLED(lightColor = Light4, darkColor = Dark3),
|
||||
ACCENT(lightColor = Meadow, darkColor = Meadow),
|
||||
WARNING(lightColor = Amaranth, darkColor = Amaranth),
|
||||
ATTENTION(lightColor = Tangerine, darkColor = Tangerine),
|
||||
CONSTANT_WHITE(lightColor = White, darkColor = White)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Colors.textColor(type: TextColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor
|
||||
9
core/ui/src/main/res/drawable/ic_copy.xml
Normal file
9
core/ui/src/main/res/drawable/ic_copy.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,21H8V7H19V21ZM19,5H8C7.47,5 6.961,5.211 6.586,5.586C6.211,5.961 6,6.47 6,7V21C6,21.53 6.211,22.039 6.586,22.414C6.961,22.789 7.47,23 8,23H19C19.53,23 20.039,22.789 20.414,22.414C20.789,22.039 21,21.53 21,21V7C21,6.47 20.789,5.961 20.414,5.586C20.039,5.211 19.53,5 19,5ZM16,1H4C3.47,1 2.961,1.211 2.586,1.586C2.211,1.961 2,2.47 2,3V17H4V3H16V1Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_discount.xml
Normal file
9
core/ui/src/main/res/drawable/ic_discount.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<path
|
||||
android:pathData="M22.04,10.71V6H17.34L14,2.69L10.71,6H6V10.71L2.69,14L6,17.34V22.04H10.71L14,25.35L17.34,22.04H22.04V17.34L25.35,14L22.04,10.71ZM10.83,9.05C11.81,9.05 12.6,9.84 12.6,10.83C12.6,11.299 12.413,11.75 12.082,12.082C11.75,12.413 11.299,12.6 10.83,12.6C9.84,12.6 9.05,11.81 9.05,10.83C9.05,9.84 9.84,9.05 10.83,9.05ZM17.22,19C16.24,19 15.45,18.2 15.45,17.22C15.45,16.751 15.637,16.3 15.968,15.968C16.3,15.637 16.751,15.45 17.22,15.45C18.2,15.45 19,16.24 19,17.22C19,17.692 18.813,18.145 18.479,18.479C18.145,18.813 17.692,19 17.22,19ZM10.5,19.03L9,17.53L17.53,9L19.03,10.5L10.5,19.03Z"
|
||||
android:fillColor="#000000" />
|
||||
</vector>
|
||||
10
core/ui/src/main/res/drawable/ic_more_vertical.xml
Normal file
10
core/ui/src/main/res/drawable/ic_more_vertical.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,8C13.1,8 14,7.1 14,6C14,4.9 13.1,4 12,4C10.9,4 10,4.9 10,6C10,7.1 10.9,8 12,8ZM12,10C10.9,10 10,10.9 10,12C10,13.1 10.9,14 12,14C13.1,14 14,13.1 14,12C14,10.9 13.1,10 12,10ZM12,16C10.9,16 10,16.9 10,18C10,19.1 10.9,20 12,20C13.1,20 14,19.1 14,18C14,16.9 13.1,16 12,16Z"
|
||||
android:fillColor="#000"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h24v24h-24z"/>
|
||||
<clip-path android:pathData="M0,0h24v24h-24z" />
|
||||
<path android:fillColor="#000000" android:pathData="M2,4H6V2H2C1.47,2 0.961,2.211 0.586,2.586C0.211,2.961 0,3.47 0,4L0,8H2V4ZM22,2H18V4H22V8H24V4C24,3.47 23.789,2.961 23.414,2.586C23.039,2.211 22.53,2 22,2ZM2,16H0V20C0,20.53 0.211,21.039 0.586,21.414C0.961,21.789 1.47,22 2,22H6V20H2V16ZM22,20H18V22H22C22.53,22 23.039,21.789 23.414,21.414C23.789,21.039 24,20.53 24,20V16H22V20ZM4,8V16C4,16.53 4.211,17.039 4.586,17.414C4.961,17.789 5.47,18 6,18H18C18.53,18 19.039,17.789 19.414,17.414C19.789,17.039 20,16.53 20,16V8C20,7.47 19.789,6.961 19.414,6.586C19.039,6.211 18.53,6 18,6H6C5.47,6 4.961,6.211 4.586,6.586C4.211,6.961 4,7.47 4,8ZM6,16V12H18V16H6ZM18,8V10H6V8H18Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_share.xml
Normal file
9
core/ui/src/main/res/drawable/ic_share.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8C18.796,8 19.559,7.684 20.121,7.121C20.684,6.559 21,5.796 21,5C21,4.204 20.684,3.441 20.121,2.879C19.559,2.316 18.796,2 18,2C17.204,2 16.441,2.316 15.879,2.879C15.316,3.441 15,4.204 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9C5.204,9 4.441,9.316 3.879,9.879C3.316,10.441 3,11.204 3,12C3,12.796 3.316,13.559 3.879,14.121C4.441,14.684 5.204,15 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19C20.92,18.226 20.612,17.483 20.065,16.935C19.517,16.388 18.774,16.08 18,16.08Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
17
core/ui/src/main/res/drawable/ic_tether.xml
Normal file
17
core/ui/src/main/res/drawable/ic_tether.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h28v28h-28z" />
|
||||
<path
|
||||
android:pathData="M8.821,17.869C9.035,16.016 9.741,14.312 10.803,12.892C10.407,13.076 9.929,13.195 9.38,13.241V14H8.552V13.247C6.83,13.134 5.744,12.22 5.738,10.863H7.015C7.077,11.556 7.657,12.04 8.552,12.142V9.483L8.015,9.346C6.67,9.017 5.935,8.234 5.935,7.093C5.935,5.749 6.929,4.852 8.552,4.709V3.902H9.38V4.709C10.941,4.84 11.966,5.766 12.003,7.063H10.744C10.719,6.424 10.176,5.922 9.38,5.826V8.342L9.941,8.467C11.454,8.82 12.163,9.561 12.163,10.768C12.163,11.028 12.13,11.27 12.065,11.493C13.628,10.047 15.64,9.079 17.869,8.822C17.808,3.939 13.831,0 8.935,0C4,0 0,4 0,8.935C0,13.831 3.939,17.808 8.821,17.869ZM17.776,10.222C13.873,10.786 10.786,13.873 10.222,17.776L17.776,10.222ZM8.552,8.174C7.688,8.007 7.225,7.589 7.225,6.991C7.225,6.352 7.781,5.862 8.552,5.82V8.174ZM9.38,12.154V9.632C10.404,9.83 10.88,10.236 10.88,10.905C10.88,11.646 10.33,12.1 9.38,12.154Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd" />
|
||||
<path
|
||||
android:pathData="M10.13,19.066C10.13,14.132 14.13,10.131 19.064,10.131C23.998,10.131 27.999,14.132 27.999,19.066C27.999,24 23.998,28.001 19.064,28.001C14.13,28.001 10.13,24 10.13,19.066ZM19.527,17.526L20.48,14.823L19.066,14.323L17.782,17.962L15.337,18.573L15.701,20.028L17.183,19.658L16.23,22.36C16.149,22.589 16.184,22.844 16.325,23.043C16.465,23.241 16.694,23.36 16.937,23.36H22.61V21.86H17.997L18.928,19.222L21.374,18.61L21.01,17.155L19.527,17.526Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd" />
|
||||
</group>
|
||||
</vector>
|
||||
BIN
core/ui/src/main/res/drawable/ill_businessman_3d.webp
Normal file
BIN
core/ui/src/main/res/drawable/ill_businessman_3d.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
core/ui/src/main/res/font/roboto_medium.ttf
Normal file
BIN
core/ui/src/main/res/font/roboto_medium.ttf
Normal file
Binary file not shown.
BIN
core/ui/src/main/res/font/roboto_regular.ttf
Normal file
BIN
core/ui/src/main/res/font/roboto_regular.ttf
Normal file
Binary file not shown.
5
core/ui/src/main/res/values-ru/string_common.xml
Normal file
5
core/ui/src/main/res/values-ru/string_common.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="common_share">Поделиться</string>
|
||||
<string name="common_copy">Копировать</string>
|
||||
</resources>
|
||||
54
core/ui/src/main/res/values/dimens.xml
Normal file
54
core/ui/src/main/res/values/dimens.xml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- region Elevation -->
|
||||
<dimen name="elevation1">1dp</dimen>
|
||||
<dimen name="elevation2">2dp</dimen>
|
||||
<dimen name="elevation3">3dp</dimen>
|
||||
<dimen name="elevation4">4dp</dimen>
|
||||
<dimen name="elevation6">6dp</dimen>
|
||||
<dimen name="elevation8">8dp</dimen>
|
||||
<dimen name="elevation12">12dp</dimen>
|
||||
<dimen name="elevation16">16dp</dimen>
|
||||
<dimen name="elevation24">24dp</dimen>
|
||||
<!-- endregion Elevation -->
|
||||
|
||||
<!-- region Radius -->
|
||||
<dimen name="radius6">6dp</dimen>
|
||||
<dimen name="radius12">12dp</dimen>
|
||||
<dimen name="radius16">16dp</dimen>
|
||||
<!-- endregion Radius -->
|
||||
|
||||
<!-- region Size -->
|
||||
<dimen name="size0">0dp</dimen>
|
||||
<dimen name="size4">4dp</dimen>
|
||||
<dimen name="size11">11dp</dimen>
|
||||
<dimen name="size16">16dp</dimen>
|
||||
<dimen name="size20">20dp</dimen>
|
||||
<dimen name="size24">24dp</dimen>
|
||||
<dimen name="size28">28dp</dimen>
|
||||
<dimen name="size32">32dp</dimen>
|
||||
<dimen name="size40">40dp</dimen>
|
||||
<dimen name="size48">48dp</dimen>
|
||||
<dimen name="size56">56dp</dimen>
|
||||
<dimen name="size84">84dp</dimen>
|
||||
<dimen name="size86">86dp</dimen>
|
||||
<dimen name="size102">102dp</dimen>
|
||||
<dimen name="size164">164dp</dimen>
|
||||
<dimen name="size200">200dp</dimen>
|
||||
<!-- endregion Size -->
|
||||
|
||||
<!-- region Spacing -->
|
||||
<dimen name="spacing2">2dp</dimen>
|
||||
<dimen name="spacing4">4dp</dimen>
|
||||
<dimen name="spacing8">8dp</dimen>
|
||||
<dimen name="spacing10">10dp</dimen>
|
||||
<dimen name="spacing12">12dp</dimen>
|
||||
<dimen name="spacing16">16dp</dimen>
|
||||
<dimen name="spacing24">24dp</dimen>
|
||||
<dimen name="spacing32">32dp</dimen>
|
||||
<dimen name="spacing44">44dp</dimen>
|
||||
<dimen name="spacing50">50dp</dimen>
|
||||
<dimen name="spacing54">54dp</dimen>
|
||||
<dimen name="radius2">2dp</dimen>
|
||||
<!-- endregion Spacing -->
|
||||
</resources>
|
||||
5
core/ui/src/main/res/values/string_common.xml
Normal file
5
core/ui/src/main/res/values/string_common.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="common_share">Share</string>
|
||||
<string name="common_copy">Copy</string>
|
||||
</resources>
|
||||
1
core/utils/.gitignore
vendored
Normal file
1
core/utils/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
35
core/utils/build.gradle.kts
Normal file
35
core/utils/build.gradle.kts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
}
|
||||
2
core/utils/src/main/AndroidManifest.xml
Normal file
2
core/utils/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.utils" />
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.utils.converter
|
||||
|
||||
interface Converter<I, O> {
|
||||
fun convert(value: I): O
|
||||
fun convertList(input: List<I>): List<O> {
|
||||
return input.map { convert(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
interface CoroutineDispatcherProvider {
|
||||
val main: CoroutineDispatcher
|
||||
val io: CoroutineDispatcher
|
||||
}
|
||||
|
||||
class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider {
|
||||
override val main: CoroutineDispatcher = Dispatchers.Main
|
||||
override val io: CoroutineDispatcher = Dispatchers.IO
|
||||
}
|
||||
|
||||
class TestingCoroutineDispatcherProvider(
|
||||
override val main: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
override val io: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
) : CoroutineDispatcherProvider
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.utils.di
|
||||
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface CoroutineDispatcherProviderModule {
|
||||
|
||||
@Binds
|
||||
fun bindCoroutineDispatcherProvider(coroutineDispatcherProvider: AppCoroutineDispatcherProvider): CoroutineDispatcherProvider
|
||||
}
|
||||
|
|
@ -75,9 +75,13 @@ dependencies {
|
|||
implementation(project(":common"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(Tangem.blockchain)
|
||||
implementation(Tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(Tangem.cardCore)
|
||||
implementation(Tangem.cardAndroid)
|
||||
implementation(Tangem.cardAndroid) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.reKotlin)
|
||||
|
|
|
|||
1
features/referral/data/.gitignore
vendored
Normal file
1
features/referral/data/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
40
features/referral/data/build.gradle.kts
Normal file
40
features/referral/data/build.gradle.kts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project*/
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
|
||||
/** Time */
|
||||
implementation(Library.jodatime)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
}
|
||||
2
features/referral/data/src/main/AndroidManifest.xml
Normal file
2
features/referral/data/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.referral.data" />
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.feature.referral.converters
|
||||
|
||||
import com.tangem.datasource.api.referral.models.ReferralResponse
|
||||
import com.tangem.datasource.api.referral.models.Token
|
||||
import com.tangem.feature.referral.domain.models.DiscountType
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
import com.tangem.feature.referral.domain.models.ReferralInfo
|
||||
import com.tangem.feature.referral.domain.models.TokenData
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReferralConverter @Inject constructor() : Converter<ReferralResponse, ReferralData> {
|
||||
|
||||
override fun convert(value: ReferralResponse): ReferralData {
|
||||
val conditions = value.conditions
|
||||
val referral = value.referral
|
||||
val tokenConverter = TokenConverter()
|
||||
return if (referral != null) {
|
||||
ReferralData.ParticipantData(
|
||||
award = conditions.award,
|
||||
discount = conditions.discount.amount,
|
||||
discountType = DiscountType.valueOf(conditions.discount.discountType.name),
|
||||
tosLink = conditions.tosLink,
|
||||
tokens = tokenConverter.convertList(conditions.awards.map { it.token }),
|
||||
referral = ReferralInfo(
|
||||
shareLink = referral.shareLink,
|
||||
address = referral.address,
|
||||
promocode = referral.promocode,
|
||||
walletsPurchased = referral.walletsPurchased,
|
||||
termsAcceptedAt = referral.termsAcceptedAt,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
ReferralData.NonParticipantData(
|
||||
award = conditions.award,
|
||||
discount = conditions.discount.amount,
|
||||
discountType = DiscountType.valueOf(conditions.discount.discountType.name),
|
||||
tosLink = conditions.tosLink,
|
||||
tokens = tokenConverter.convertList(conditions.awards.map { it.token }),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenConverter : Converter<Token, TokenData> {
|
||||
|
||||
override fun convert(value: Token): TokenData {
|
||||
return TokenData(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = value.networkId,
|
||||
contractAddress = value.contractAddress,
|
||||
decimalCount = value.decimalCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.feature.referral.data
|
||||
|
||||
import com.tangem.datasource.api.referral.ReferralApi
|
||||
import com.tangem.datasource.api.referral.models.StartReferralBody
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class ReferralRepositoryImpl(
|
||||
private val referralApi: ReferralApi,
|
||||
private val referralConverter: ReferralConverter,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
) : ReferralRepository {
|
||||
|
||||
override suspend fun getReferralStatus(walletId: String): ReferralData {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
referralConverter.convert(referralApi.getReferralStatus(walletId))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun startReferral(
|
||||
walletId: String,
|
||||
networkId: String,
|
||||
tokenId: String,
|
||||
address: String,
|
||||
): ReferralData {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
referralConverter.convert(
|
||||
referralApi.startReferral(
|
||||
StartReferralBody(
|
||||
walletId = walletId,
|
||||
networkId = networkId,
|
||||
tokenId = tokenId,
|
||||
address = address,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.feature.referral.di
|
||||
|
||||
import com.tangem.datasource.api.referral.ReferralApi
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class ReferralRepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideReferralRepository(
|
||||
referralApi: ReferralApi,
|
||||
referralConverter: ReferralConverter,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): ReferralRepository {
|
||||
return ReferralRepositoryImpl(
|
||||
referralApi, referralConverter, coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
1
features/referral/domain/.gitignore
vendored
Normal file
1
features/referral/domain/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
39
features/referral/domain/build.gradle.kts
Normal file
39
features/referral/domain/build.gradle.kts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Libs */
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":libs:crypto"))
|
||||
|
||||
/** Time */
|
||||
implementation(Library.jodatime)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
}
|
||||
2
features/referral/domain/src/main/AndroidManifest.xml
Normal file
2
features/referral/domain/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.referral.domain" />
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.referral.domain
|
||||
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
|
||||
interface ReferralInteractor {
|
||||
|
||||
suspend fun getReferralStatus(): ReferralData
|
||||
|
||||
suspend fun startReferral(): ReferralData
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.feature.referral.domain
|
||||
|
||||
import com.tangem.feature.referral.domain.converter.TokensConverter
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
import com.tangem.feature.referral.domain.models.TokenData
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
|
||||
internal class ReferralInteractorImpl(
|
||||
private val repository: ReferralRepository,
|
||||
private val derivationManager: DerivationManager,
|
||||
private val userWalletManager: UserWalletManager,
|
||||
private val tokensConverter: TokensConverter,
|
||||
) : ReferralInteractor {
|
||||
|
||||
private val tokensForReferral = mutableListOf<TokenData>()
|
||||
|
||||
override suspend fun getReferralStatus(): ReferralData {
|
||||
val refStatus = repository.getReferralStatus(userWalletManager.getWalletId())
|
||||
saveRefTokens(refStatus.tokens)
|
||||
return refStatus
|
||||
}
|
||||
|
||||
override suspend fun startReferral(): ReferralData {
|
||||
if (tokensForReferral.isNotEmpty()) {
|
||||
val currency = tokensConverter.convert(tokensForReferral.first())
|
||||
deriveOrAddTokens(currency)
|
||||
val publicAddress = userWalletManager.getWalletAddress(currency)
|
||||
return repository.startReferral(
|
||||
walletId = userWalletManager.getWalletId(),
|
||||
networkId = currency.networkId,
|
||||
tokenId = currency.id,
|
||||
address = publicAddress,
|
||||
)
|
||||
} else {
|
||||
throw IllegalStateException("tokens for ref is empty")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deriveOrAddTokens(currency: Currency) {
|
||||
if (!derivationManager.hasDerivation(currency.networkId)) {
|
||||
derivationManager.deriveMissingBlockchains(currency)
|
||||
}
|
||||
if (!userWalletManager.isTokenAdded(currency)) {
|
||||
userWalletManager.addToken(currency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveRefTokens(tokens: List<TokenData>) {
|
||||
tokensForReferral.clear()
|
||||
tokensForReferral.addAll(tokens)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.feature.referral.domain
|
||||
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
|
||||
interface ReferralRepository {
|
||||
|
||||
/** Returns data object of [ReferralData] depends on user program status */
|
||||
suspend fun getReferralStatus(walletId: String): ReferralData
|
||||
|
||||
/** Starts user referral program */
|
||||
suspend fun startReferral(
|
||||
walletId: String,
|
||||
networkId: String,
|
||||
tokenId: String,
|
||||
address: String,
|
||||
): ReferralData
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.feature.referral.domain.converter
|
||||
|
||||
import com.tangem.feature.referral.domain.models.TokenData
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.NativeToken
|
||||
import com.tangem.lib.crypto.models.NonNativeToken
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class TokensConverter @Inject constructor() : Converter<TokenData, Currency> {
|
||||
|
||||
override fun convert(value: TokenData): Currency {
|
||||
return if (value.decimalCount != null &&
|
||||
value.contractAddress != null
|
||||
) {
|
||||
NonNativeToken(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = value.networkId,
|
||||
contractAddress = value.contractAddress,
|
||||
decimalCount = value.decimalCount,
|
||||
)
|
||||
} else {
|
||||
NativeToken(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = value.networkId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.feature.referral.domain.di
|
||||
|
||||
import com.tangem.feature.referral.domain.ReferralInteractor
|
||||
import com.tangem.feature.referral.domain.ReferralInteractorImpl
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.feature.referral.domain.converter.TokensConverter
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class ReferralDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideReferralInteractor(
|
||||
referralRepository: ReferralRepository,
|
||||
derivationManager: DerivationManager,
|
||||
userWalletManager: UserWalletManager,
|
||||
tokensConverter: TokensConverter,
|
||||
): ReferralInteractor {
|
||||
return ReferralInteractorImpl(
|
||||
referralRepository, derivationManager, userWalletManager, tokensConverter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.feature.referral.domain.models
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
sealed interface ReferralData {
|
||||
|
||||
val award: Int
|
||||
val discount: Int
|
||||
val discountType: DiscountType
|
||||
val tosLink: String
|
||||
val tokens: List<TokenData>
|
||||
|
||||
/** Data class that used if user is participant of program */
|
||||
data class ParticipantData(
|
||||
override val award: Int,
|
||||
override val discount: Int,
|
||||
override val discountType: DiscountType,
|
||||
override val tosLink: String,
|
||||
override val tokens: List<TokenData>,
|
||||
val referral: ReferralInfo,
|
||||
) : ReferralData
|
||||
|
||||
/** Data class that used if user is not participant of program */
|
||||
data class NonParticipantData(
|
||||
override val award: Int,
|
||||
override val discount: Int,
|
||||
override val discountType: DiscountType,
|
||||
override val tosLink: String,
|
||||
override val tokens: List<TokenData>,
|
||||
) : ReferralData
|
||||
}
|
||||
|
||||
data class TokenData(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val networkId: String,
|
||||
val contractAddress: String?,
|
||||
val decimalCount: Int?,
|
||||
)
|
||||
|
||||
data class ReferralInfo(
|
||||
val shareLink: String,
|
||||
val address: String,
|
||||
val promocode: String,
|
||||
val walletsPurchased: Int,
|
||||
val termsAcceptedAt: DateTime?,
|
||||
)
|
||||
|
||||
enum class DiscountType {
|
||||
PERCENTAGE, VALUE
|
||||
}
|
||||
1
features/referral/presentation/.gitignore
vendored
Normal file
1
features/referral/presentation/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
56
features/referral/presentation/build.gradle.kts
Normal file
56
features/referral/presentation/build.gradle.kts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = Versions.compose
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":core:ui"))
|
||||
|
||||
/** AndroidX */
|
||||
implementation(AndroidX.appCompat)
|
||||
|
||||
/** Compose */
|
||||
implementation(Compose.foundation)
|
||||
implementation(Compose.material)
|
||||
implementation(Compose.uiTooling)
|
||||
|
||||
/** Domain */
|
||||
implementation(project(":features:referral:domain"))
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.composeShimmer)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.referral.presentation" />
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.feature.referral.models
|
||||
|
||||
data class ReferralStateHolder(
|
||||
val headerState: HeaderState,
|
||||
val referralInfoState: ReferralInfoState,
|
||||
val effects: Effects,
|
||||
val agreementBottomSheetState: AgreementBottomSheetState
|
||||
) {
|
||||
|
||||
data class HeaderState(val onBackClicked: () -> Unit)
|
||||
|
||||
sealed interface ReferralInfoContentState
|
||||
|
||||
sealed interface ReferralInfoState {
|
||||
|
||||
data class ParticipantContent(
|
||||
val award: String,
|
||||
val address: String,
|
||||
val discount: String,
|
||||
val onParticipateClicked: () -> Unit,
|
||||
) : ReferralInfoState, ReferralInfoContentState
|
||||
|
||||
data class NonParticipantContent(
|
||||
val award: String,
|
||||
val discount: String,
|
||||
val purchasedWalletCount: Int,
|
||||
val code: String,
|
||||
val onCopyClicked: () -> Unit,
|
||||
val onShareClicked: () -> Unit,
|
||||
) : ReferralInfoState, ReferralInfoContentState
|
||||
|
||||
object Loading : ReferralInfoState
|
||||
}
|
||||
|
||||
data class Effects(val showErrorToast: Boolean)
|
||||
|
||||
data class AgreementBottomSheetState(val url: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.feature.referral.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.feature.referral.domain.ReferralInteractor
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ReferralViewModel @Inject constructor(referralInteractor: ReferralInteractor) : ViewModel() {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons
|
||||
import com.tangem.core.ui.components.atoms.BottomSheetIndicator
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
/**
|
||||
* Bottom sheet content with referral program terms of use
|
||||
*
|
||||
* @param url link to the html page
|
||||
*/
|
||||
@Composable
|
||||
fun AgreementBottomSheetContent(url: String) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colors.secondary)
|
||||
.fillMaxWidth()
|
||||
.height(LocalConfiguration.current.screenHeightDp.dp - dimensionResource(id = R.dimen.spacing16)),
|
||||
) {
|
||||
BottomSheetIndicator()
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
AppBarWithAdditionalButtons(text = stringResource(id = R.string.details_referral_title))
|
||||
AgreementHtmlView(url = url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AgreementHtmlView(url: String) {
|
||||
AndroidView(
|
||||
modifier = Modifier.background(MaterialTheme.colors.secondary),
|
||||
factory = {
|
||||
WebView(it).apply {
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
webViewClient = WebViewClient()
|
||||
settings.apply {
|
||||
javaScriptEnabled = false
|
||||
allowFileAccess = false
|
||||
}
|
||||
loadUrl(url)
|
||||
}
|
||||
},
|
||||
update = { it.loadUrl(url) },
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun Preview_AgreementBottomSheet_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun Preview_AgreementBottomSheet_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.textColor
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AgreementText_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
Box(modifier = Modifier.background(MaterialTheme.colors.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_AgreementText_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
Box(modifier = Modifier.background(MaterialTheme.colors.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) {
|
||||
val agreementText = annotatedAgreementString(firstPart = stringResource(firstPartResId))
|
||||
ClickableText(
|
||||
text = agreementText,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.spacing54)),
|
||||
style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center),
|
||||
maxLines = 2,
|
||||
onClick = {
|
||||
val clickableSpanStyle = requireNotNull(agreementText.spanStyles.getOrNull(1))
|
||||
if (it in clickableSpanStyle.start..clickableSpanStyle.end) {
|
||||
onClicked()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun annotatedAgreementString(firstPart: String): AnnotatedString {
|
||||
return buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) {
|
||||
append("$firstPart ")
|
||||
}
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.ACCENT))) {
|
||||
append(stringResource(id = R.string.common_terms_and_conditions))
|
||||
}
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) {
|
||||
append(" ${stringResource(id = R.string.referral_tou_suffix)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryStartIconButton
|
||||
import com.tangem.core.ui.components.SmallInfoCard
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.textColor
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_NonParticipateBottomBlock_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
Column(Modifier.background(MaterialTheme.colors.primary)) {
|
||||
NonParticipateBottomBlock(
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
onAgreementClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_NonParticipateBottomBlock_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
Column(Modifier.background(MaterialTheme.colors.primary)) {
|
||||
NonParticipateBottomBlock(
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
onAgreementClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NonParticipateBottomBlock(
|
||||
purchasedWalletCount: Int,
|
||||
code: String,
|
||||
onCopyClicked: () -> Unit,
|
||||
onShareClicked: () -> Unit,
|
||||
onAgreementClicked: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = dimensionResource(id = R.dimen.spacing24),
|
||||
bottom = dimensionResource(id = R.dimen.spacing16),
|
||||
)
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.spacing16)),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)),
|
||||
) {
|
||||
SmallInfoCard(
|
||||
startText = stringResource(id = R.string.referral_friends_bought_title),
|
||||
endText = String.format(
|
||||
stringResource(id = R.string.referral_wallets_purchased_count),
|
||||
purchasedWalletCount,
|
||||
),
|
||||
)
|
||||
PersonalCodeCard(code = code)
|
||||
AdditionalButtons(onCopyClicked = onCopyClicked, onShareClicked = onShareClicked)
|
||||
AgreementText(firstPartResId = R.string.referral_tou_enroled_prefix, onClicked = onAgreementClicked)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PersonalCodeCard(code: String) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.shadow(
|
||||
elevation = dimensionResource(id = R.dimen.elevation2),
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
)
|
||||
.background(
|
||||
color = MaterialTheme.colors.secondary,
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = dimensionResource(id = R.dimen.spacing12)),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing4)),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.referral_promo_code_title),
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
)
|
||||
Text(
|
||||
text = code,
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.h2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AdditionalButtons(onCopyClicked: () -> Unit, onShareClicked: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)),
|
||||
) {
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_copy),
|
||||
iconResId = R.drawable.ic_copy,
|
||||
onClicked = onCopyClicked,
|
||||
)
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share,
|
||||
onClicked = onShareClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryEndIconButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ParticipateBottomBlock_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
Column(Modifier.background(MaterialTheme.colors.primary)) {
|
||||
ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ParticipateBottomBlock_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
Column(Modifier.background(MaterialTheme.colors.primary)) {
|
||||
ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) {
|
||||
Column {
|
||||
AgreementText(
|
||||
firstPartResId = R.string.referral_tou_not_enroled_prefix,
|
||||
onClicked = onAgreementClicked,
|
||||
)
|
||||
PrimaryEndIconButton(
|
||||
modifier = Modifier.padding(all = dimensionResource(id = R.dimen.spacing16)),
|
||||
text = stringResource(id = R.string.referral_button_participate),
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
onClicked = onParticipateClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,453 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.BottomSheetScaffold
|
||||
import androidx.compose.material.BottomSheetState
|
||||
import androidx.compose.material.BottomSheetValue
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.VerticalSpacer
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.ButtonColorType
|
||||
import com.tangem.core.ui.res.IconColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.White
|
||||
import com.tangem.core.ui.res.buttonColor
|
||||
import com.tangem.core.ui.res.iconColor
|
||||
import com.tangem.core.ui.res.textColor
|
||||
import com.tangem.feature.referral.models.ReferralStateHolder
|
||||
import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Referral program screen for participant and non-participant
|
||||
*
|
||||
* @param stateHolder state holder
|
||||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ReferralScreen(stateHolder: ReferralStateHolder) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed),
|
||||
)
|
||||
|
||||
TangemTheme {
|
||||
BottomSheetScaffold(
|
||||
sheetContent = { AgreementBottomSheetContent(stateHolder.agreementBottomSheetState.url) },
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
sheetShape = RoundedCornerShape(
|
||||
topStart = dimensionResource(id = R.dimen.radius16),
|
||||
topEnd = dimensionResource(id = R.dimen.radius16),
|
||||
),
|
||||
sheetElevation = dimensionResource(id = R.dimen.elevation24),
|
||||
sheetPeekHeight = dimensionResource(id = R.dimen.size0),
|
||||
content = {
|
||||
ReferralContent(
|
||||
stateHolder = stateHolder,
|
||||
onAgreementClicked = {
|
||||
coroutineScope.launch {
|
||||
if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) {
|
||||
bottomSheetScaffoldState.bottomSheetState.expand()
|
||||
} else {
|
||||
bottomSheetScaffoldState.bottomSheetState.collapse()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colors.primary)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Header(stateHolder = stateHolder)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing16)
|
||||
ReferralInfo(stateHolder = stateHolder, onAgreementClicked = onAgreementClicked)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.Header(stateHolder: ReferralStateHolder) {
|
||||
AppBar(stateHolder = stateHolder)
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ill_businessman_3d),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.spacing32))
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(R.dimen.size200)),
|
||||
)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing24)
|
||||
Text(
|
||||
text = stringResource(id = R.string.referral_title),
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)),
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1),
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
style = MaterialTheme.typography.h2,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) {
|
||||
when (val state = stateHolder.referralInfoState) {
|
||||
is ReferralInfoState.ParticipantContent -> {
|
||||
Conditions(state = state)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing44)
|
||||
ParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked)
|
||||
}
|
||||
is ReferralInfoState.NonParticipantContent -> {
|
||||
Conditions(state = state)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing24)
|
||||
NonParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked)
|
||||
}
|
||||
is ReferralInfoState.Loading -> {
|
||||
LoadingCondition(iconResId = R.drawable.ic_tether)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing32)
|
||||
LoadingCondition(iconResId = R.drawable.ic_discount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(stateHolder: ReferralStateHolder) {
|
||||
AppBarWithBackButton(
|
||||
text = stringResource(R.string.details_referral_title),
|
||||
onBackClick = stateHolder.headerState.onBackClicked,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Conditions(state: ReferralStateHolder.ReferralInfoContentState) {
|
||||
ConditionForYou(state = state)
|
||||
VerticalSpacer(spaceResId = R.dimen.spacing32)
|
||||
ConditionForYourFriend(state = state)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConditionForYou(state: ReferralStateHolder.ReferralInfoContentState) {
|
||||
Condition(iconResId = R.drawable.ic_tether) {
|
||||
when (state) {
|
||||
is ReferralInfoState.ParticipantContent -> InfoForYou(award = state.award, address = state.address)
|
||||
is ReferralInfoState.NonParticipantContent -> InfoForYou(award = state.award)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConditionForYourFriend(state: ReferralStateHolder.ReferralInfoContentState) {
|
||||
Condition(iconResId = R.drawable.ic_discount) {
|
||||
when (state) {
|
||||
is ReferralInfoState.ParticipantContent -> InfoForYourFriend(discount = state.discount)
|
||||
is ReferralInfoState.NonParticipantContent -> InfoForYourFriend(discount = state.discount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingCondition(@DrawableRes iconResId: Int) {
|
||||
Condition(iconResId = iconResId) {
|
||||
ShimmerInfo()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing16)),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing12)),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colors.buttonColor(type = ButtonColorType.SECONDARY),
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius16)),
|
||||
)
|
||||
.size(dimensionResource(id = R.dimen.size56)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(dimensionResource(id = R.dimen.size28)),
|
||||
tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1),
|
||||
)
|
||||
}
|
||||
infoBlock()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoForYou(award: String, address: String? = null) {
|
||||
ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(stringResource(id = R.string.referral_point_currencies_description_prefix))
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1))) {
|
||||
append(" $award ")
|
||||
}
|
||||
append(
|
||||
String.format(
|
||||
stringResource(id = R.string.referral_point_currencies_description_suffix),
|
||||
if (!address.isNullOrBlank()) address else "",
|
||||
),
|
||||
)
|
||||
},
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoForYourFriend(discount: String) {
|
||||
ConditionInfo(title = stringResource(id = R.string.referral_point_discount_title)) {
|
||||
Text(
|
||||
text = buildString {
|
||||
append(stringResource(id = R.string.referral_point_discount_description_prefix))
|
||||
append(
|
||||
String.format(
|
||||
stringResource(id = R.string.referral_point_discount_description_value),
|
||||
" $discount",
|
||||
),
|
||||
)
|
||||
append(" ")
|
||||
append(stringResource(id = R.string.referral_point_discount_description_suffix))
|
||||
},
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConditionInfo(title: String, subtitleContent: @Composable () -> Unit) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing2))) {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
)
|
||||
subtitleContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShimmerInfo() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = dimensionResource(id = R.dimen.spacing4))
|
||||
.shimmer(),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing10)),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6)))
|
||||
.width(dimensionResource(id = R.dimen.size102))
|
||||
.height(dimensionResource(id = R.dimen.size16))
|
||||
.background(White),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6)))
|
||||
.width(dimensionResource(id = R.dimen.size40))
|
||||
.height(dimensionResource(id = R.dimen.size11))
|
||||
.background(White),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent, onAgreementClicked: () -> Unit) {
|
||||
ParticipateBottomBlock(
|
||||
onAgreementClicked = onAgreementClicked,
|
||||
onParticipateClicked = state.onParticipateClicked,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent, onAgreementClicked: () -> Unit) {
|
||||
NonParticipateBottomBlock(
|
||||
purchasedWalletCount = state.purchasedWalletCount,
|
||||
code = state.code,
|
||||
onCopyClicked = state.onCopyClicked,
|
||||
onShareClicked = state.onShareClicked,
|
||||
onAgreementClicked = onAgreementClicked,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_Participant_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.ParticipantContent(
|
||||
award = "10 USDT",
|
||||
address = "ma80...zk8q2",
|
||||
discount = "10%",
|
||||
onParticipateClicked = {},
|
||||
),
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_Participant_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.ParticipantContent(
|
||||
award = "10 USDT",
|
||||
address = "ma80...zk8q2",
|
||||
discount = "10%",
|
||||
|
||||
onParticipateClicked = {},
|
||||
),
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.NonParticipantContent(
|
||||
award = "10 USDT",
|
||||
discount = "10%",
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
|
||||
),
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_NonParticipant_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.NonParticipantContent(
|
||||
award = "10 USDT",
|
||||
discount = "10%",
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
|
||||
),
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_Loading_InLightTheme() {
|
||||
TangemTheme(isDarkTheme = false) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.Loading,
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_ReferralScreen_Loading_InDarkTheme() {
|
||||
TangemTheme(isDarkTheme = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.Loading,
|
||||
effects = ReferralStateHolder.Effects(showErrorToast = false),
|
||||
agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState(
|
||||
url = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="details_referral_title">Реферальная программа</string>
|
||||
<string name="referral_title">Приведи друга в Tangem</string>
|
||||
<string name="referral_point_currencies_title">Вы</string>
|
||||
<string name="referral_point_currencies_description_prefix">Получите</string>
|
||||
<string name="referral_point_currencies_description_suffix">за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %s</string>
|
||||
<string name="referral_point_discount_title">Ваш друг</string>
|
||||
<string name="referral_point_discount_description_prefix">Получит</string>
|
||||
<string name="referral_point_discount_description_value">%s скидку</string>
|
||||
<string name="referral_point_discount_description_suffix">при покупке карточки на сайте tangem.com</string>
|
||||
<string name="referral_tou_not_enroled_prefix">Нажимая на эту кнопку вы принимаете</string>
|
||||
<string name="common_terms_and_conditions">условия участия</string>
|
||||
<string name="referral_tou_suffix">в реферальной программе</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_tou_enroled_prefix">Вы приняли</string>
|
||||
<string name="referral_promo_code_title">Ваш персональный код</string>
|
||||
<string name="referral_wallets_purchased_count">%d кошельков</string>
|
||||
<string name="referral_friends_bought_title">Ваши друзья купили</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="details_referral_title">Referral program</string>
|
||||
<string name="referral_title">Refer your friends to Tangem</string>
|
||||
<string name="referral_point_currencies_title">You</string>
|
||||
<string name="referral_point_currencies_description_prefix">Will get</string>
|
||||
<string name="referral_point_currencies_description_suffix">for each wallet bought by your friend on your Tron network address %s</string>
|
||||
<string name="referral_point_discount_title">Your friend</string>
|
||||
<string name="referral_point_discount_description_prefix">Will get a</string>
|
||||
<string name="referral_point_discount_description_value">%s discount</string>
|
||||
<string name="referral_point_discount_description_suffix">when buying a card on tangem.com</string>
|
||||
<string name="referral_tou_not_enroled_prefix">By tapping this button you accept</string>
|
||||
<string name="common_terms_and_conditions">terms and conditions</string>
|
||||
<string name="referral_tou_suffix">of the referral program</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_tou_enroled_prefix">You\'ve accepted</string>
|
||||
<string name="referral_promo_code_title">Your personal code</string>
|
||||
<string name="referral_wallets_purchased_count">%d wallets</string>
|
||||
<string name="referral_friends_bought_title">Your friends bought</string>
|
||||
</resources>
|
||||
1
libs/crypto/.gitignore
vendored
Normal file
1
libs/crypto/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
29
libs/crypto/build.gradle.kts
Normal file
29
libs/crypto/build.gradle.kts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
}
|
||||
2
libs/crypto/src/main/AndroidManifest.xml
Normal file
2
libs/crypto/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.lib.crypto" />
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
|
||||
interface DerivationManager {
|
||||
|
||||
/**
|
||||
* Derives missing blockchain and returns flag true if did it successfully
|
||||
*
|
||||
* @param currency to derive (Native token or not)
|
||||
*/
|
||||
suspend fun deriveMissingBlockchains(currency: Currency): Boolean
|
||||
|
||||
/**
|
||||
* Checks that given [networkId] has derivations
|
||||
*/
|
||||
fun hasDerivation(networkId: String): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
|
||||
/**
|
||||
* Provider for user tokens data
|
||||
*/
|
||||
interface UserWalletManager {
|
||||
|
||||
/**
|
||||
* Returns all user tokens (merged from local and backend)
|
||||
*/
|
||||
suspend fun getUserTokens(): List<Currency>
|
||||
|
||||
/**
|
||||
* Returns user walletId
|
||||
*/
|
||||
fun getWalletId(): String
|
||||
|
||||
/**
|
||||
* Checks that token added to user wallet
|
||||
*
|
||||
* @param currency to receive referral payments
|
||||
*/
|
||||
suspend fun isTokenAdded(currency: Currency): Boolean
|
||||
|
||||
/**
|
||||
* Adds token to wallet if its not
|
||||
*
|
||||
* @param currency to add to wallet
|
||||
*/
|
||||
fun addToken(currency: Currency)
|
||||
|
||||
/**
|
||||
* Returns wallet public address for token
|
||||
*
|
||||
* @param currency for which find address
|
||||
*/
|
||||
fun getWalletAddress(currency: Currency): String
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
/**
|
||||
* Currency data class that can be native blockchain Token
|
||||
* or custom Token (used in this lib to replace and divide logic Currency from app module)
|
||||
*/
|
||||
abstract class Currency(
|
||||
open val id: String,
|
||||
open val name: String,
|
||||
open val symbol: String,
|
||||
open val networkId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
data class NativeToken(
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
override val symbol: String,
|
||||
override val networkId: String,
|
||||
) : Currency(id, name, symbol, networkId)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
class NonNativeToken(
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
override val symbol: String,
|
||||
override val networkId: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
) : Currency(id, name, symbol, networkId)
|
||||
|
|
@ -1,4 +1,20 @@
|
|||
include(":app")
|
||||
include(":domain")
|
||||
include(":network")
|
||||
include(":common")
|
||||
include(":common")
|
||||
|
||||
// region Core modules
|
||||
include(":core:datasource")
|
||||
include(":core:utils")
|
||||
include(":core:ui")
|
||||
// endregion Core modules
|
||||
|
||||
// region Libs modules
|
||||
include(":libs:crypto")
|
||||
// endregion Libs modules
|
||||
|
||||
// region Feature modules
|
||||
include(":features:referral:data")
|
||||
include(":features:referral:domain")
|
||||
include(":features:referral:presentation")
|
||||
// endregion Feature modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue