mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-23 08:27:57 +00:00
Initial commit: Pezkuwi Wallet Android
Complete rebrand of Nova Wallet for Pezkuwichain ecosystem. ## Features - Full Pezkuwichain support (HEZ & PEZ tokens) - Polkadot ecosystem compatibility - Staking, Governance, DeFi, NFTs - XCM cross-chain transfers - Hardware wallet support (Ledger, Polkadot Vault) - WalletConnect v2 - Push notifications ## Languages - English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese Based on Nova Wallet by Novasama Technologies GmbH © Dijital Kurdistan Tech Institute 2026
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.data.config
|
||||
|
||||
import io.novafoundation.nova.feature_ahm_impl.BuildConfig
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface ChainMigrationConfigApi {
|
||||
|
||||
@GET(BuildConfig.AHM_CONFIG_URL)
|
||||
suspend fun getConfig(): List<ChainMigrationConfigRemote>
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.data.config
|
||||
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfig
|
||||
import java.math.BigInteger
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class ChainMigrationConfigRemote(
|
||||
val sourceData: ChainData,
|
||||
val destinationData: ChainData,
|
||||
val blockNumber: BigInteger,
|
||||
val timestamp: Long,
|
||||
val newTokenNames: List<String>,
|
||||
val bannerPath: String,
|
||||
val migrationInProgress: Boolean,
|
||||
val wikiURL: String
|
||||
) {
|
||||
|
||||
class ChainData(
|
||||
val chainId: String,
|
||||
val assetId: Int,
|
||||
val minBalance: BigInteger,
|
||||
val averageFee: BigInteger
|
||||
)
|
||||
}
|
||||
|
||||
fun ChainMigrationConfigRemote.toDomain(): ChainMigrationConfig {
|
||||
return ChainMigrationConfig(
|
||||
originData = sourceData.toDomain(),
|
||||
destinationData = destinationData.toDomain(),
|
||||
blockNumberStartAt = blockNumber,
|
||||
timeStartAt = Date(timestamp.seconds.inWholeMilliseconds),
|
||||
newTokenNames = newTokenNames,
|
||||
bannerPath = bannerPath,
|
||||
migrationInProgress = migrationInProgress,
|
||||
wikiURL = wikiURL
|
||||
)
|
||||
}
|
||||
|
||||
fun ChainMigrationConfigRemote.ChainData.toDomain(): ChainMigrationConfig.ChainData {
|
||||
return ChainMigrationConfig.ChainData(
|
||||
chainId = chainId,
|
||||
assetId = assetId,
|
||||
minBalance = minBalance,
|
||||
averageFee = averageFee
|
||||
)
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.domain.balance.totalBalance
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
import io.novafoundation.nova.core_db.dao.AssetDao
|
||||
import io.novafoundation.nova.core_db.model.AssetLocal
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
|
||||
import io.novafoundation.nova.runtime.ext.UTILITY_ASSET_ID
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.hash.isPositive
|
||||
|
||||
private const val CHAINS_WITH_ASSET_BALANCE = "CHAINS_WITH_ASSET_BALANCE"
|
||||
private const val CHAIN_MIGRATION_INFO_SHOWN_PREFIX = "CHAIN_MIGRATION_INFO_SHOWN_PREFIX_"
|
||||
|
||||
class RealChainMigrationRepository(
|
||||
private val assetDao: AssetDao,
|
||||
private val preferences: Preferences
|
||||
) : ChainMigrationRepository {
|
||||
|
||||
override suspend fun cacheBalancesForChainMigrationDetection() {
|
||||
val chainsWithAssetBalance = assetDao.getAssetsById(id = UTILITY_ASSET_ID)
|
||||
.filter { it.hasBalance() }
|
||||
.mapToSet { it.chainId }
|
||||
|
||||
saveToStorage(chainsWithAssetBalance)
|
||||
}
|
||||
|
||||
override fun isMigrationDetailsWasShown(chainId: String): Boolean {
|
||||
return preferences.getBoolean(getChainInfoShownKey(chainId), false)
|
||||
}
|
||||
|
||||
override fun isChainMigrationDetailsNeeded(chainId: String): Boolean {
|
||||
return chainId in getChainsWithAssetBalance()
|
||||
}
|
||||
|
||||
override suspend fun setInfoShownForChain(chainId: String) {
|
||||
preferences.putBoolean(getChainInfoShownKey(chainId), true)
|
||||
}
|
||||
|
||||
private fun AssetLocal.hasBalance(): Boolean {
|
||||
return totalBalance(freeInPlanks, reservedInPlanks).isPositive()
|
||||
}
|
||||
|
||||
private fun saveToStorage(chainsWithAssetBalance: Set<String>) {
|
||||
val currentChains = getChainsWithAssetBalance()
|
||||
setChainsWithAssetBalance(currentChains + chainsWithAssetBalance)
|
||||
}
|
||||
|
||||
private fun getChainsWithAssetBalance() = preferences.getStringSet(CHAINS_WITH_ASSET_BALANCE)
|
||||
|
||||
private fun setChainsWithAssetBalance(chains: Set<ChainId>) = preferences.putStringSet(CHAINS_WITH_ASSET_BALANCE, chains)
|
||||
|
||||
private fun getChainInfoShownKey(chainId: String) = CHAIN_MIGRATION_INFO_SHOWN_PREFIX + chainId
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.memory.SingleValueCache
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfig
|
||||
import io.novafoundation.nova.feature_ahm_impl.data.config.ChainMigrationConfigApi
|
||||
import io.novafoundation.nova.feature_ahm_impl.data.config.toDomain
|
||||
|
||||
class RealMigrationInfoRepository(
|
||||
private val api: ChainMigrationConfigApi
|
||||
) : MigrationInfoRepository {
|
||||
|
||||
private val config = SingleValueCache {
|
||||
val configResponse = api.getConfig()
|
||||
configResponse.map { it.toDomain() }
|
||||
}
|
||||
|
||||
override suspend fun getConfigByOriginChain(chainId: String): ChainMigrationConfig? {
|
||||
return getConfigsInternal().getOrNull()?.firstOrNull { it.originData.chainId == chainId }
|
||||
}
|
||||
|
||||
override suspend fun getConfigByDestinationChain(chainId: String): ChainMigrationConfig? {
|
||||
return getConfigsInternal().getOrNull()?.firstOrNull { it.destinationData.chainId == chainId }
|
||||
}
|
||||
|
||||
override suspend fun getAllConfigs(): List<ChainMigrationConfig> {
|
||||
return getConfigsInternal().getOrNull() ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun loadConfigs() {
|
||||
getConfigsInternal()
|
||||
}
|
||||
|
||||
private suspend fun getConfigsInternal() = runCatching {
|
||||
config()
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.di
|
||||
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.di.ChainMigrationDetailsComponent
|
||||
import io.novafoundation.nova.feature_banners_api.di.BannersFeatureApi
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
ChainMigrationFeatureDependencies::class,
|
||||
],
|
||||
modules = [
|
||||
ChainMigrationFeatureModule::class,
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface ChainMigrationFeatureComponent : ChainMigrationFeatureApi {
|
||||
|
||||
fun chainMigrationDetailsComponentFactory(): ChainMigrationDetailsComponent.Factory
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance router: ChainMigrationRouter,
|
||||
deps: ChainMigrationFeatureDependencies
|
||||
): ChainMigrationFeatureComponent
|
||||
}
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class,
|
||||
DbApi::class,
|
||||
RuntimeApi::class,
|
||||
BannersFeatureApi::class,
|
||||
WalletFeatureApi::class
|
||||
]
|
||||
)
|
||||
interface AccountFeatureDependenciesComponent : ChainMigrationFeatureDependencies
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.di
|
||||
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.repository.ToggleFeatureRepository
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.core_db.dao.AssetDao
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSourceFactory
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.TokenFormatter
|
||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import javax.inject.Named
|
||||
|
||||
interface ChainMigrationFeatureDependencies {
|
||||
|
||||
val resourceManager: ResourceManager
|
||||
|
||||
val promotionBannersMixinFactory: PromotionBannersMixinFactory
|
||||
|
||||
val bannersSourceFactory: BannersSourceFactory
|
||||
|
||||
val assetDao: AssetDao
|
||||
|
||||
val preferences: Preferences
|
||||
|
||||
val chainRegistry: ChainRegistry
|
||||
|
||||
val apiCreator: NetworkApiCreator
|
||||
|
||||
val automaticInteractionGate: AutomaticInteractionGate
|
||||
|
||||
val toggleFeatureRepository: ToggleFeatureRepository
|
||||
|
||||
val chainStateRepository: ChainStateRepository
|
||||
|
||||
val tokenFormatter: TokenFormatter
|
||||
|
||||
@Named(REMOTE_STORAGE_SOURCE)
|
||||
fun remoteStorageSource(): StorageDataSource
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.di
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureApiHolder
|
||||
import io.novafoundation.nova.common.di.FeatureContainer
|
||||
import io.novafoundation.nova.common.di.scope.ApplicationScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_banners_api.di.BannersFeatureApi
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScope
|
||||
class ChainMigrationFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer,
|
||||
private val router: ChainMigrationRouter,
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val accountFeatureDependencies = DaggerChainMigrationFeatureComponent_AccountFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.dbApi(getFeature(DbApi::class.java))
|
||||
.runtimeApi(getFeature(RuntimeApi::class.java))
|
||||
.bannersFeatureApi(getFeature(BannersFeatureApi::class.java))
|
||||
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
|
||||
.build()
|
||||
|
||||
return DaggerChainMigrationFeatureComponent.factory()
|
||||
.create(
|
||||
router = router,
|
||||
deps = accountFeatureDependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.repository.ToggleFeatureRepository
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core_db.dao.AssetDao
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationDetailsSelectToShowUseCase
|
||||
import io.novafoundation.nova.feature_ahm_impl.data.config.ChainMigrationConfigApi
|
||||
import io.novafoundation.nova.feature_ahm_impl.data.repository.RealChainMigrationRepository
|
||||
import io.novafoundation.nova.feature_ahm_impl.data.repository.RealMigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_impl.di.modules.DeepLinkModule
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationInfoUseCase
|
||||
import io.novafoundation.nova.feature_ahm_impl.domain.ChainMigrationDetailsInteractor
|
||||
import io.novafoundation.nova.feature_ahm_impl.domain.RealChainMigrationInfoUseCase
|
||||
import io.novafoundation.nova.feature_ahm_impl.domain.RealChainMigrationDetailsSelectToShowUseCase
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
|
||||
@Module(
|
||||
includes = [DeepLinkModule::class]
|
||||
)
|
||||
class ChainMigrationFeatureModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideChainMigrationConfigApi(
|
||||
apiCreator: NetworkApiCreator
|
||||
): ChainMigrationConfigApi {
|
||||
return apiCreator.create(ChainMigrationConfigApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideChainMigrationRepository(
|
||||
assetDao: AssetDao,
|
||||
preferences: Preferences,
|
||||
): ChainMigrationRepository {
|
||||
return RealChainMigrationRepository(
|
||||
assetDao,
|
||||
preferences
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMigrationInfoRepository(
|
||||
api: ChainMigrationConfigApi
|
||||
): MigrationInfoRepository {
|
||||
return RealMigrationInfoRepository(api)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideChainMigrationDetailsInteractor(
|
||||
chainRegistry: ChainRegistry,
|
||||
chainMigrationRepository: ChainMigrationRepository,
|
||||
migrationInfoRepository: MigrationInfoRepository
|
||||
): ChainMigrationDetailsInteractor {
|
||||
return ChainMigrationDetailsInteractor(
|
||||
chainRegistry,
|
||||
chainMigrationRepository,
|
||||
migrationInfoRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAssetMigrationUseCase(
|
||||
migrationInfoRepository: MigrationInfoRepository,
|
||||
toggleFeatureRepository: ToggleFeatureRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
chainStateRepository: ChainStateRepository
|
||||
): ChainMigrationInfoUseCase {
|
||||
return RealChainMigrationInfoUseCase(
|
||||
migrationInfoRepository,
|
||||
toggleFeatureRepository,
|
||||
chainRegistry,
|
||||
chainStateRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideChainMigrationDetailsSelectToShowUseCase(
|
||||
chainMigrationRepository: ChainMigrationRepository,
|
||||
migrationInfoRepository: MigrationInfoRepository,
|
||||
chainStateRepository: ChainStateRepository
|
||||
): ChainMigrationDetailsSelectToShowUseCase {
|
||||
return RealChainMigrationDetailsSelectToShowUseCase(
|
||||
migrationInfoRepository,
|
||||
chainMigrationRepository,
|
||||
chainStateRepository
|
||||
)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.di.modules
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.feature_ahm_api.di.deeplinks.ChainMigrationDeepLinks
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.deeplink.ChainMigrationDetailsDeepLinkHandler
|
||||
|
||||
@Module
|
||||
class DeepLinkModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideStakingDashboardDeepLinkHandler(
|
||||
router: ChainMigrationRouter,
|
||||
automaticInteractionGate: AutomaticInteractionGate
|
||||
) = ChainMigrationDetailsDeepLinkHandler(
|
||||
router,
|
||||
automaticInteractionGate
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideDeepLinks(stakingDashboard: ChainMigrationDetailsDeepLinkHandler): ChainMigrationDeepLinks {
|
||||
return ChainMigrationDeepLinks(listOf(stakingDashboard))
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.domain
|
||||
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfig
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chainFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ChainMigrationDetailsInteractor(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val chainMigrationRepository: ChainMigrationRepository,
|
||||
private val migrationInfoRepository: MigrationInfoRepository
|
||||
) {
|
||||
|
||||
fun chainFlow(chainId: String): Flow<Chain> {
|
||||
return chainRegistry.chainFlow(chainId)
|
||||
}
|
||||
|
||||
suspend fun getChain(chainId: String): Chain {
|
||||
return chainRegistry.getChain(chainId)
|
||||
}
|
||||
|
||||
suspend fun getChainMigrationConfig(chainId: String): ChainMigrationConfig? {
|
||||
return migrationInfoRepository.getConfigByOriginChain(chainId)
|
||||
}
|
||||
|
||||
suspend fun markMigrationInfoAlreadyShown(chainId: String) {
|
||||
chainMigrationRepository.setInfoShownForChain(chainId)
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.domain
|
||||
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationDetailsSelectToShowUseCase
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
|
||||
class RealChainMigrationDetailsSelectToShowUseCase(
|
||||
private val migrationInfoRepository: MigrationInfoRepository,
|
||||
private val chainMigrationRepository: ChainMigrationRepository,
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
) : ChainMigrationDetailsSelectToShowUseCase {
|
||||
|
||||
override suspend fun getChainIdsToShowMigrationDetails(): List<String> {
|
||||
val configs = migrationInfoRepository.getAllConfigs()
|
||||
return configs
|
||||
.filter {
|
||||
val detailsWasNotShown = !chainMigrationRepository.isMigrationDetailsWasShown(it.originData.chainId)
|
||||
val chainRequireMigrationDetails = chainMigrationRepository.isChainMigrationDetailsNeeded(it.originData.chainId)
|
||||
detailsWasNotShown && chainRequireMigrationDetails && chainStateRepository.isMigrationBlockPassed(it)
|
||||
}
|
||||
.map { it.originData.chainId }
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.domain
|
||||
|
||||
import io.novafoundation.nova.common.data.repository.ToggleFeatureRepository
|
||||
import io.novafoundation.nova.common.utils.flowOfAll
|
||||
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationInfoUseCase
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfigWithChains
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealChainMigrationInfoUseCase(
|
||||
private val migrationInfoRepository: MigrationInfoRepository,
|
||||
private val toggleFeatureRepository: ToggleFeatureRepository,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val chainStateRepository: ChainStateRepository
|
||||
) : ChainMigrationInfoUseCase {
|
||||
|
||||
override fun observeMigrationConfigOrNull(chainId: String, assetId: Int): Flow<ChainMigrationConfigWithChains?> = flowOfAll {
|
||||
val config = migrationInfoRepository.getConfigByOriginChain(chainId)
|
||||
?: migrationInfoRepository.getConfigByDestinationChain(chainId)
|
||||
?: return@flowOfAll emptyFlow()
|
||||
|
||||
if (chainStateRepository.isMigrationBlockNotPassed(config)) return@flowOfAll emptyFlow()
|
||||
|
||||
chainRegistry.chainsById
|
||||
.map {
|
||||
val sourceChain = it.getValue(config.originData.chainId)
|
||||
val destinationChain = it.getValue(config.destinationData.chainId)
|
||||
|
||||
ChainMigrationConfigWithChains(
|
||||
config = config,
|
||||
originChain = sourceChain,
|
||||
originAsset = sourceChain.assetsById.getValue(config.originData.assetId),
|
||||
destinationChain = destinationChain,
|
||||
destinationAsset = destinationChain.assetsById.getValue(config.destinationData.assetId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeInfoShouldBeHidden(key: String, chainId: String, assetId: Int): Flow<Boolean> {
|
||||
return toggleFeatureRepository.observe(getKeyFor(key, chainId, assetId))
|
||||
}
|
||||
|
||||
override fun markMigrationInfoAsHidden(key: String, chainId: String, assetId: Int) {
|
||||
toggleFeatureRepository.set(getKeyFor(key, chainId, assetId), true)
|
||||
}
|
||||
|
||||
private fun getKeyFor(key: String, chainId: String, assetId: Int): String {
|
||||
return "$key-$chainId-$assetId"
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.domain
|
||||
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfig
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
|
||||
suspend fun ChainStateRepository.isMigrationBlockPassed(config: ChainMigrationConfig): Boolean {
|
||||
return currentRemoteBlock(config.originData.chainId) > config.blockNumberStartAt
|
||||
}
|
||||
|
||||
suspend fun ChainStateRepository.isMigrationBlockNotPassed(config: ChainMigrationConfig): Boolean {
|
||||
return !isMigrationBlockPassed(config)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation
|
||||
|
||||
import io.novafoundation.nova.common.navigation.ReturnableRouter
|
||||
|
||||
interface ChainMigrationRouter : ReturnableRouter {
|
||||
|
||||
fun openChainMigrationDetails(chainId: String)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.hints.setHints
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.utils.FragmentPayloadCreator
|
||||
import io.novafoundation.nova.common.utils.PayloadCreator
|
||||
import io.novafoundation.nova.common.utils.payload
|
||||
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
|
||||
import io.novafoundation.nova.feature_ahm_impl.di.ChainMigrationFeatureComponent
|
||||
import io.novafoundation.nova.feature_ahm_impl.databinding.FragmentChainMigrationDetailsBinding
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.bind
|
||||
|
||||
class ChainMigrationDetailsFragment : BaseFragment<ChainMigrationDetailsViewModel, FragmentChainMigrationDetailsBinding>() {
|
||||
|
||||
companion object : PayloadCreator<ChainMigrationDetailsPayload> by FragmentPayloadCreator()
|
||||
|
||||
override fun createBinding() = FragmentChainMigrationDetailsBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.chainMigrationDetailsButton.setOnClickListener { viewModel.okButtonClicked() }
|
||||
|
||||
binder.chainMigrationDetailsToolbar.setRightActionClickListener { viewModel.learnMoreClicked() }
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<ChainMigrationFeatureComponent>(
|
||||
requireContext(),
|
||||
ChainMigrationFeatureApi::class.java
|
||||
).chainMigrationDetailsComponentFactory()
|
||||
.create(this, payload())
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: ChainMigrationDetailsViewModel) {
|
||||
observeBrowserEvents(viewModel)
|
||||
|
||||
viewModel.bannersFlow.observe {
|
||||
it.bind(binder.chainMigrationDetailsBanner)
|
||||
}
|
||||
|
||||
viewModel.configUIFlow.observe {
|
||||
binder.chainMigrationDetailsTitle.text = it.title
|
||||
binder.chainMigrationDetailsMinBalance.text = it.minimalBalance
|
||||
binder.chainMigrationDetailsLowerFee.text = it.lowerFee
|
||||
binder.chainMigrationDetailsTokens.text = it.tokens
|
||||
binder.chainMigrationDetailsAccess.text = it.unifiedAccess
|
||||
binder.chainMigrationDetailsAnyTokenFee.text = it.anyTokenFee
|
||||
binder.chainMigrationDetailsHints.setHints(it.hints)
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class ChainMigrationDetailsPayload(val chainId: String) : Parcelable
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.mixin.hints.HintModel
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.common.utils.formatting.format
|
||||
import io.novafoundation.nova.common.utils.launchUnit
|
||||
import io.novafoundation.nova.feature_ahm_api.domain.model.ChainMigrationConfig
|
||||
import io.novafoundation.nova.feature_ahm_api.presentation.getChainMigrationDateFormat
|
||||
import io.novafoundation.nova.feature_ahm_impl.R
|
||||
import io.novafoundation.nova.feature_ahm_impl.domain.ChainMigrationDetailsInteractor
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSourceFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.forDirectory
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.TokenFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatToken
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ChainMigrationDetailsViewModel(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val router: ChainMigrationRouter,
|
||||
private val interactor: ChainMigrationDetailsInteractor,
|
||||
private val payload: ChainMigrationDetailsPayload,
|
||||
private val promotionBannersMixinFactory: PromotionBannersMixinFactory,
|
||||
private val bannerSourceFactory: BannersSourceFactory,
|
||||
private val tokenFormatter: TokenFormatter
|
||||
) : BaseViewModel(), Browserable {
|
||||
|
||||
override val openBrowserEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
private val dateFormatter = getChainMigrationDateFormat()
|
||||
|
||||
private val chainFlow = interactor.chainFlow(payload.chainId)
|
||||
.shareInBackground()
|
||||
|
||||
private val configFlow = flowOf { interactor.getChainMigrationConfig(payload.chainId) }
|
||||
.filterNotNull()
|
||||
.shareInBackground()
|
||||
|
||||
val bannersFlow = configFlow.map {
|
||||
promotionBannersMixinFactory.create(bannerSourceFactory.forDirectory(it.bannerPath), this)
|
||||
}.shareInBackground()
|
||||
|
||||
val configUIFlow = combine(configFlow, chainFlow) { config, sourceChain ->
|
||||
val destinationChain = interactor.getChain(config.destinationData.chainId)
|
||||
val sourceAsset = sourceChain.assetsById.getValue(config.originData.assetId)
|
||||
val destinationAsset = destinationChain.assetsById.getValue(config.destinationData.assetId)
|
||||
val tokenSymbol = sourceAsset.symbol.value
|
||||
val newTokens = config.newTokenNames.joinToString()
|
||||
|
||||
val minimalBalanceScale = config.originData.minBalance / config.destinationData.minBalance
|
||||
val lowerFeeScale = config.originData.averageFee / config.destinationData.averageFee
|
||||
|
||||
ConfigModel(
|
||||
title = getTitle(config, tokenSymbol, destinationChain),
|
||||
minimalBalance = resourceManager.getString(
|
||||
R.string.chain_migration_details_minimal_balance,
|
||||
minimalBalanceScale.format(),
|
||||
tokenFormatter.formatToken(config.originData.minBalance, sourceAsset),
|
||||
tokenFormatter.formatToken(config.destinationData.minBalance, destinationAsset),
|
||||
),
|
||||
lowerFee = resourceManager.getString(
|
||||
R.string.chain_migration_details_lower_fee,
|
||||
lowerFeeScale.format(),
|
||||
tokenFormatter.formatToken(config.originData.averageFee, sourceAsset),
|
||||
tokenFormatter.formatToken(config.destinationData.averageFee, destinationAsset)
|
||||
),
|
||||
tokens = resourceManager.getString(R.string.chain_migration_details_tokens, newTokens),
|
||||
unifiedAccess = resourceManager.getString(R.string.chain_migration_details_unified_access, tokenSymbol),
|
||||
anyTokenFee = resourceManager.getString(R.string.chain_migration_details_fee_in_any_tokens),
|
||||
hints = listOf(
|
||||
HintModel(R.drawable.ic_recent_history, resourceManager.getString(R.string.chain_migration_details_hint_history, sourceChain.name)),
|
||||
HintModel(R.drawable.ic_pezkuwi, resourceManager.getString(R.string.chain_migration_details_hint_auto_migration))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getTitle(config: ChainMigrationConfig, tokenSymbol: String, destinationChain: Chain): String {
|
||||
val formattedDate = dateFormatter.format(config.timeStartAt)
|
||||
return if (config.migrationInProgress) {
|
||||
resourceManager.getString(R.string.chain_migration_details_in_progress_title, formattedDate, tokenSymbol, destinationChain.name)
|
||||
} else {
|
||||
resourceManager.getString(R.string.chain_migration_details_title, formattedDate, tokenSymbol, destinationChain.name)
|
||||
}
|
||||
}
|
||||
|
||||
fun okButtonClicked() = launchUnit {
|
||||
interactor.markMigrationInfoAlreadyShown(payload.chainId)
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun learnMoreClicked() {
|
||||
launch {
|
||||
val config = configFlow.first()
|
||||
|
||||
openBrowserEvent.value = Event(config.wikiURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails
|
||||
|
||||
import io.novafoundation.nova.common.mixin.hints.HintModel
|
||||
|
||||
class ConfigModel(
|
||||
val title: String,
|
||||
val minimalBalance: String,
|
||||
val lowerFee: String,
|
||||
val tokens: String,
|
||||
val unifiedAccess: String,
|
||||
val anyTokenFee: String,
|
||||
val hints: List<HintModel>
|
||||
)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.deeplink
|
||||
|
||||
import android.net.Uri
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.handling.CallbackEvent
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
|
||||
import java.security.InvalidParameterException
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
private const val STAKING_DASHBOARD_DEEP_LINK_PREFIX = "/open/ahm"
|
||||
|
||||
class ChainMigrationDetailsDeepLinkHandler(
|
||||
private val chainMigrationRouter: ChainMigrationRouter,
|
||||
private val automaticInteractionGate: AutomaticInteractionGate
|
||||
) : DeepLinkHandler {
|
||||
|
||||
override val callbackFlow: Flow<CallbackEvent> = emptyFlow()
|
||||
|
||||
override suspend fun matches(data: Uri): Boolean {
|
||||
val path = data.path ?: return false
|
||||
return path.startsWith(STAKING_DASHBOARD_DEEP_LINK_PREFIX)
|
||||
}
|
||||
|
||||
override suspend fun handleDeepLink(data: Uri) = runCatching {
|
||||
automaticInteractionGate.awaitInteractionAllowed()
|
||||
|
||||
val chainId = data.getQueryParameter("chainId") ?: throw InvalidParameterException()
|
||||
|
||||
chainMigrationRouter.openChainMigrationDetails(chainId)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.ChainMigrationDetailsFragment
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.ChainMigrationDetailsPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
ChainMigrationDetailsModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface ChainMigrationDetailsComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: ChainMigrationDetailsPayload
|
||||
): ChainMigrationDetailsComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: ChainMigrationDetailsFragment)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ahm_impl.domain.ChainMigrationDetailsInteractor
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.ChainMigrationDetailsPayload
|
||||
import io.novafoundation.nova.feature_ahm_impl.presentation.migrationDetails.ChainMigrationDetailsViewModel
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSourceFactory
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.TokenFormatter
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class ChainMigrationDetailsModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(ChainMigrationDetailsViewModel::class)
|
||||
fun provideViewModel(
|
||||
resourceManager: ResourceManager,
|
||||
router: ChainMigrationRouter,
|
||||
interactor: ChainMigrationDetailsInteractor,
|
||||
payload: ChainMigrationDetailsPayload,
|
||||
promotionBannersMixinFactory: PromotionBannersMixinFactory,
|
||||
bannerSourceFactory: BannersSourceFactory,
|
||||
tokenFormatter: TokenFormatter
|
||||
): ViewModel {
|
||||
return ChainMigrationDetailsViewModel(
|
||||
resourceManager,
|
||||
router,
|
||||
interactor,
|
||||
payload,
|
||||
promotionBannersMixinFactory,
|
||||
bannerSourceFactory,
|
||||
tokenFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): ChainMigrationDetailsViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(ChainMigrationDetailsViewModel::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/chainMigrationDetailsToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:homeButtonVisible="false"
|
||||
app:textRight="@string/common_learn_more" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<io.novafoundation.nova.feature_banners_api.presentation.view.BannerPagerView
|
||||
android:id="@+id/chainMigrationDetailsBanner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsTitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Bold.Title3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/text_primary"
|
||||
tools:text="Starting October 7, 2025 your KSM balance, Staking and Governance are now on Kusama Asset Hub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsSubtitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/chain_migration_details_subtitle"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="👛" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsMinBalance"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
tools:text="100x reduction in minimal balance (from 0.00033 KSM to 0.0000033 KSM)" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="💸" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chain_migration_details_lower_fee"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
tools:text="10x lower transaction fees
(from 0.0005 KSM to 0.00005 KSM)" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="🪙" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsTokens"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
tools:text="More tokens supported: USDT, USDC, ETH, DOT, and other ecosystem tokens" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="🗂️" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsAccess"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
tools:text="Unified access to KSM, assets, staking, and governance" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="🧾" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chainMigrationDetailsAnyTokenFee"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
tools:text="Ability to pay fees in any token" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/divider" />
|
||||
|
||||
<io.novafoundation.nova.common.mixin.hints.HintsView
|
||||
android:id="@+id/chainMigrationDetailsHints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButtonV2
|
||||
android:id="@+id/chainMigrationDetailsButton"
|
||||
style="@style/Widget.Nova.MaterialButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/common_got_it" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user