From c1810c6d63fe1a1edf1fff7ab3f2848be264e803 Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 12 Jul 2024 14:00:35 +0700 Subject: [PATCH 01/84] nomis request testing Signed-off-by: Deneath --- .../jp/co/soramitsu/app/root/domain/RootInteractor.kt | 6 ++++++ .../soramitsu/app/root/presentation/RootViewModel.kt | 10 ++++++++++ .../common/data/network/config/RemoteConfigFetcher.kt | 6 ++++++ .../wallet/impl/domain/interfaces/WalletRepository.kt | 1 + .../impl/data/repository/WalletRepositoryImpl.kt | 4 ++++ 5 files changed, 27 insertions(+) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt b/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt index aea13fed5f..e2364e90b1 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt @@ -67,6 +67,12 @@ class RootInteractor( } } + suspend fun testNomisVitalikScore(): Result { + return withContext(Dispatchers.Default) { + walletRepository.testNomisVitalikScore() + } + } + fun chainRegistrySyncUp() = chainRegistry.syncUp() suspend fun fetchFeatureToggle() = withContext(Dispatchers.Default) { pendulumPreInstalledAccountsScenario.fetchFeatureToggle() } diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt index 40b3426436..55b54fcc64 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.app.root.presentation +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope @@ -70,12 +71,21 @@ class RootViewModel @Inject constructor( } private suspend fun syncConfigs() { + interactor.testNomisVitalikScore() + .onFailure { + Log.d("&&&", "failed test nomis score: $it") + } + .onSuccess { + Log.d("&&&", "successful test nomis score: $it") + } + coroutineScope { checkAppVersion() interactor.fetchFeatureToggle() interactor.syncChainsConfigs().onFailure { _showNoInternetConnectionAlert.value = Event(Unit) } + } } diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt index 78b526405e..ddff718c87 100644 --- a/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt @@ -15,4 +15,10 @@ interface RemoteConfigFetcher { @GET(BuildConfig.FEATURE_TOGGLE_URL) suspend fun getFeatureToggle(): FeatureToggleConfig + + @GET("https://api.nomis.cc/api/v1/multichain-score/wallet/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/score") + suspend fun getNomisVitalikScore( + @Header("X-API-Key") apiKey: String = "j9Us1Kxoo9fs3nD", + @Header("X-ClientId") clientId: String = "FCEB90FC-E3F9-4CF5-980E-A8111A3FFF31" + ): String } diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt index 6cef5f3930..9e20c2eee7 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt @@ -107,4 +107,5 @@ interface WalletRepository { suspend fun estimateClaimRewardsFee(chainId: ChainId): BigInteger suspend fun claimRewards(chain: IChain, accountId: AccountId): Result suspend fun updateAssetsHidden(state: List) + suspend fun testNomisVitalikScore(): Result } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt index af82143e3f..50f530e9b7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt @@ -549,6 +549,10 @@ class WalletRepositoryImpl( return kotlin.runCatching { remoteConfigFetcher.getAppConfig() } } + override suspend fun testNomisVitalikScore(): Result { + return kotlin.runCatching {remoteConfigFetcher.getNomisVitalikScore()} + } + override suspend fun getControllerAccount(chainId: ChainId, accountId: AccountId): AccountId? { return substrateSource.getControllerAccount(chainId, accountId) } From 4716c3f8d227178b754c4f5f08dfe39f52117927 Mon Sep 17 00:00:00 2001 From: Deneath Date: Mon, 22 Jul 2024 12:16:37 +0700 Subject: [PATCH 02/84] nomis wip add http client, add db table Signed-off-by: Deneath --- .../network/config/RemoteConfigFetcher.kt | 6 ---- .../common/data/network/nomis/NomisApi.kt | 11 +++++++ .../common/di/modules/NetworkModule.kt | 33 +++++++++++++++++++ .../soramitsu/coredb/migrations/Migrations.kt | 8 +++++ .../coredb/model/NomisWalletScoreLocal.kt | 32 ++++++++++++++++++ .../data/repository/WalletRepositoryImpl.kt | 6 ++-- .../wallet/impl/di/WalletFeatureModule.kt | 7 ++-- 7 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt create mode 100644 core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt index ddff718c87..78b526405e 100644 --- a/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/config/RemoteConfigFetcher.kt @@ -15,10 +15,4 @@ interface RemoteConfigFetcher { @GET(BuildConfig.FEATURE_TOGGLE_URL) suspend fun getFeatureToggle(): FeatureToggleConfig - - @GET("https://api.nomis.cc/api/v1/multichain-score/wallet/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/score") - suspend fun getNomisVitalikScore( - @Header("X-API-Key") apiKey: String = "j9Us1Kxoo9fs3nD", - @Header("X-ClientId") clientId: String = "FCEB90FC-E3F9-4CF5-980E-A8111A3FFF31" - ): String } diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt new file mode 100644 index 0000000000..7ca072658a --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt @@ -0,0 +1,11 @@ +package jp.co.soramitsu.common.data.network.nomis + +import retrofit2.http.GET +import retrofit2.http.Path + +interface NomisApi { + @GET("wallet/{address}/score/") + suspend fun getNomisScore( + @Path("address") address: String + ): String +} \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt index 4af739ce87..c6720757a7 100644 --- a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt +++ b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt @@ -15,6 +15,7 @@ import jp.co.soramitsu.common.data.network.AndroidLogger import jp.co.soramitsu.common.data.network.AppLinksProvider import jp.co.soramitsu.common.data.network.HttpExceptionHandler import jp.co.soramitsu.common.data.network.NetworkApiCreator +import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.network.rpc.SocketSingleRequestExecutor import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.shared_utils.wsrpc.SocketService @@ -25,10 +26,14 @@ import jp.co.soramitsu.shared_utils.wsrpc.request.RequestExecutor import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory private const val HTTP_CACHE = "http_cache" private const val CACHE_SIZE = 50L * 1024L * 1024L // 50 MiB private const val TIMEOUT_SECONDS = 60L +private const val NOMIS_TIMEOUT_MINUTES = 5L @InstallIn(SingletonComponent::class) @Module @@ -66,6 +71,34 @@ class NetworkModule { return builder.build() } + @Provides + @Singleton + fun provideNomisHttpClient(): NomisApi { + val builder = OkHttpClient.Builder() + .connectTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .writeTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .readTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .addInterceptor { + val r = it.request().newBuilder().apply { + addHeader("X-API-Key", "j9Us1Kxoo9fs3nD") + addHeader("X-ClientId", "FCEB90FC-E3F9-4CF5-980E-A8111A3FFF31") + }.build() + it.proceed(r) + } + .retryOnConnectionFailure(true) + + val gson = Gson() + + val retrofit = Retrofit.Builder() + .client(builder.build()) + .baseUrl("https://api.nomis.cc/api/v1/multichain-score/") + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build() + + return retrofit.create(NomisApi::class.java) + } + @Provides @Singleton fun provideLogger(): Logger = AndroidLogger() diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt index 8e0864323f..2b49e3199a 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt @@ -3,6 +3,14 @@ package jp.co.soramitsu.coredb.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +val Migration_67_68 = object : Migration(67, 68) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE meta_accounts SET initialized = 0") + db.execSQL("UPDATE chain_accounts SET initialized = 0") + db.execSQL("DELETE FROM assets") + } +} + val Migration_66_67 = object : Migration(66, 67) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("UPDATE meta_accounts SET initialized = 0") diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt new file mode 100644 index 0000000000..315845f72e --- /dev/null +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.coredb.model + +import androidx.room.Entity +import androidx.room.ForeignKey +import java.math.BigDecimal +import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal + +@Entity( + tableName = "nomis_wallet_score", + primaryKeys = ["metaId"], + foreignKeys = [ + ForeignKey( + entity = MetaAccountLocal::class, + parentColumns = ["id"], + childColumns = ["metaId"], + onDelete = ForeignKey.CASCADE + ) + ], +) +data class NomisWalletScoreLocal( + val metaId: Long, + val score: Int, + val updated: Long, + val nativeBalanceUsd: BigDecimal, + val holdTokensUsd: BigDecimal, + val walletAge: Long, + val totalTransactions: Long, + val rejectedTransactions: Long, + val avgTransactionTime: Long, + val maxTransactionTime: Long, + val minTransactionTime: Long +) \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt index 50f530e9b7..a0f78876ce 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt @@ -10,6 +10,7 @@ import jp.co.soramitsu.common.data.network.HttpExceptionHandler import jp.co.soramitsu.common.data.network.coingecko.CoingeckoApi import jp.co.soramitsu.common.data.network.config.AppConfigRemote import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher +import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.network.runtime.binding.UseCaseBinding import jp.co.soramitsu.common.data.network.runtime.binding.bindNumber import jp.co.soramitsu.common.data.network.runtime.binding.bindString @@ -101,7 +102,8 @@ class WalletRepositoryImpl( private val accountRepository: AccountRepository, private val chainsRepository: ChainsRepository, private val extrinsicService: ExtrinsicService, - private val remoteStorageSource: StorageDataSource + private val remoteStorageSource: StorageDataSource, + private val nomisApi: NomisApi ) : WalletRepository, UpdatesProviderUi by updatesMixin { companion object { @@ -550,7 +552,7 @@ class WalletRepositoryImpl( } override suspend fun testNomisVitalikScore(): Result { - return kotlin.runCatching {remoteConfigFetcher.getNomisVitalikScore()} + return kotlin.runCatching { nomisApi.getNomisScore("0xd8da6bf26964af9d7eed9e03e53415d37aa96045") } } override suspend fun getControllerAccount(chainId: ChainId, accountId: AccountId): AccountId? { diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt index 9553b77470..6c5a482226 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt @@ -20,6 +20,7 @@ import jp.co.soramitsu.common.data.network.HttpExceptionHandler import jp.co.soramitsu.common.data.network.NetworkApiCreator import jp.co.soramitsu.common.data.network.coingecko.CoingeckoApi import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher +import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.common.domain.GetAvailableFiatCurrencies import jp.co.soramitsu.common.domain.NetworkStateService @@ -177,7 +178,8 @@ class WalletFeatureModule { chainsRepository: ChainsRepository, extrinsicService: ExtrinsicService, @Named(REMOTE_STORAGE_SOURCE) - remoteStorageSource: StorageDataSource + remoteStorageSource: StorageDataSource, + nomisApi: NomisApi ): WalletRepository = WalletRepositoryImpl( substrateSource, ethereumRemoteSource, @@ -195,7 +197,8 @@ class WalletFeatureModule { accountRepository, chainsRepository, extrinsicService, - remoteStorageSource + remoteStorageSource, + nomisApi ) @Provides From 5328f68d3ce4c557779c4d8a1a83cca278ddd484 Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 2 Aug 2024 10:30:42 +0700 Subject: [PATCH 03/84] WIP Signed-off-by: Deneath --- .../app/root/domain/RootInteractor.kt | 6 - .../app/root/navigation/Navigator.kt | 5 + .../app/root/presentation/RootViewModel.kt | 10 - .../main/res/navigation/main_nav_graph.xml | 5 + .../common/compose/component/Address.kt | 19 +- .../common/compose/component/EmptyMessage.kt | 12 + .../common/compose/component/ScoreStar.kt | 64 ++++ .../common/compose/component/Toolbar.kt | 125 +++++--- .../common/compose/component/WalletItem.kt | 12 +- .../soramitsu/common/compose/theme/Color.kt | 1 + .../common/data/network/nomis/NomisApi.kt | 2 +- .../data/network/nomis/NomisResponse.kt | 29 ++ .../common/di/modules/NetworkModule.kt | 11 +- .../main/res/drawable/ic_score_star_empty.xml | 16 + .../main/res/drawable/ic_score_star_full.xml | 9 + .../main/res/drawable/ic_score_star_half.xml | 9 + common/src/main/res/values-in/strings.xml | 1 + common/src/main/res/values-ja/strings.xml | 21 ++ common/src/main/res/values-pt/strings.xml | 1 + common/src/main/res/values-ru/strings.xml | 26 ++ common/src/main/res/values-tr/strings.xml | 21 ++ common/src/main/res/values-vi/strings.xml | 32 +- common/src/main/res/values-zh/strings.xml | 38 ++- common/src/main/res/values/strings.xml | 40 +++ .../jp/co/soramitsu/coredb/dao/Helpers.kt | 3 +- .../jp/co/soramitsu/coredb/AppDatabase.kt | 13 +- .../co/soramitsu/coredb/dao/NomisScoresDao.kt | 26 ++ .../jp/co/soramitsu/coredb/di/DbModule.kt | 9 +- .../soramitsu/coredb/migrations/Migrations.kt | 25 ++ .../coredb/model/NomisWalletScoreLocal.kt | 45 ++- .../domain/interfaces/AccountInteractor.kt | 6 + .../domain/interfaces/AccountRepository.kt | 4 + .../api/domain/model/NomisScoreData.kt | 21 ++ .../account/impl/data/mappers/Mappers.kt | 44 +++ .../data/repository/AccountRepositoryImpl.kt | 26 +- .../account/impl/di/AccountFeatureModule.kt | 7 +- .../impl/domain/AccountInteractorImpl.kt | 23 ++ .../account/impl/domain/WalletSyncService.kt | 68 ++++ .../nomis_scoring/ScoreDetailsFragment.kt | 32 ++ .../nomis_scoring/ScoreDetailsScreen.kt | 301 ++++++++++++++++++ .../nomis_scoring/ScoreDetailsViewModel.kt | 105 ++++++ .../nft/impl/presentation/NFTFlowFragment.kt | 2 +- .../domain/interfaces/WalletRepository.kt | 1 - .../data/repository/WalletRepositoryImpl.kt | 9 +- .../wallet/impl/di/WalletFeatureModule.kt | 13 +- .../wallet/impl/presentation/WalletRouter.kt | 2 + .../assetDetails/AssetDetailsScreen.kt | 6 +- .../assetDetails/AssetDetailsViewModel.kt | 3 +- .../balance/detail/BalanceDetailFragment.kt | 2 +- .../balance/detail/BalanceDetailViewModel.kt | 3 +- .../balance/list/BalanceListFragment.kt | 47 +-- .../balance/list/BalanceListViewModel.kt | 78 +++-- .../walletselector/SelectWalletContent.kt | 9 +- .../walletselector/SelectWalletFragment.kt | 3 +- .../walletselector/SelectWalletViewModel.kt | 40 ++- .../light/WalletSelectorViewModel.kt | 46 ++- 56 files changed, 1327 insertions(+), 210 deletions(-) create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt create mode 100644 common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisResponse.kt create mode 100644 common/src/main/res/drawable/ic_score_star_empty.xml create mode 100644 common/src/main/res/drawable/ic_score_star_full.xml create mode 100644 common/src/main/res/drawable/ic_score_star_half.xml create mode 100644 core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt create mode 100644 feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt create mode 100644 feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt create mode 100644 feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt create mode 100644 feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt diff --git a/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt b/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt index e2364e90b1..aea13fed5f 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/domain/RootInteractor.kt @@ -67,12 +67,6 @@ class RootInteractor( } } - suspend fun testNomisVitalikScore(): Result { - return withContext(Dispatchers.Default) { - walletRepository.testNomisVitalikScore() - } - } - fun chainRegistrySyncUp() = chainRegistry.syncUp() suspend fun fetchFeatureToggle() = withContext(Dispatchers.Default) { pendulumPreInstalledAccountsScenario.fetchFeatureToggle() } diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 2ca6d0a52a..d934bf4d6e 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -46,6 +46,7 @@ import jp.co.soramitsu.account.impl.presentation.node.add.AddNodeFragment import jp.co.soramitsu.account.impl.presentation.node.details.NodeDetailsFragment import jp.co.soramitsu.account.impl.presentation.node.details.NodeDetailsPayload import jp.co.soramitsu.account.impl.presentation.node.list.NodesFragment +import jp.co.soramitsu.account.impl.presentation.nomis_scoring.ScoreDetailsFragment import jp.co.soramitsu.account.impl.presentation.options_switch_node.OptionsSwitchNodeFragment import jp.co.soramitsu.account.impl.presentation.optionsaddaccount.OptionsAddAccountFragment import jp.co.soramitsu.account.impl.presentation.pincode.PinCodeAction @@ -1511,4 +1512,8 @@ class Navigator : override fun openServiceScreen() { navController?.navigate(R.id.serviceFragment) } + + override fun openScoreDetailsScreen(metaId: Long) { + navController?.navigate(R.id.scoreDetailsFragment, ScoreDetailsFragment.getBundle(metaId)) + } } diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt index 55b54fcc64..40b3426436 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/RootViewModel.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.app.root.presentation -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope @@ -71,21 +70,12 @@ class RootViewModel @Inject constructor( } private suspend fun syncConfigs() { - interactor.testNomisVitalikScore() - .onFailure { - Log.d("&&&", "failed test nomis score: $it") - } - .onSuccess { - Log.d("&&&", "successful test nomis score: $it") - } - coroutineScope { checkAppVersion() interactor.fetchFeatureToggle() interactor.syncChainsConfigs().onFailure { _showNoInternetConnectionAlert.value = Event(Unit) } - } } diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 699ee2d05f..cc762fd355 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -1362,4 +1362,9 @@ android:name="jp.co.soramitsu.wallet.impl.presentation.beacon.sign.TransactionRawDataFragment" android:label="TransactionRawDataFragment" /> + + \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/Address.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/Address.kt index cf628e5c28..51e9fe1239 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/Address.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/Address.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.common.compose.component -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -13,16 +12,15 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.common.R -import jp.co.soramitsu.common.compose.theme.FearlessTheme -import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.common.utils.formatting.shortenAddress @Composable @@ -32,10 +30,9 @@ fun Address( onClick: () -> Unit ) { Surface( - modifier.background( - color = MaterialTheme.customColors.white08, - shape = RoundedCornerShape(100.dp) - ) + color = white08, + shape = RoundedCornerShape(100.dp), + modifier = modifier ) { Row( modifier = Modifier @@ -45,7 +42,7 @@ fun Address( Text( text = address.shortenAddress(), style = MaterialTheme.customTypography.body2, - color = Color.White, + color = white, maxLines = 1, modifier = Modifier .testTag("address") @@ -68,12 +65,10 @@ fun Address( @Preview @Composable private fun AddressPreview() { - FearlessTheme { - Surface(Modifier.background(Color.Black)) { + FearlessAppTheme { Address( address = "0x32141235qwegtf24315reqwerfasdgqwert243rfasdvgergsdf", onClick = {} ) - } } } diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/EmptyMessage.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/EmptyMessage.kt index 57731bbd03..cd138ec77c 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/EmptyMessage.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/EmptyMessage.kt @@ -9,8 +9,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme import jp.co.soramitsu.common.compose.theme.gray2 @Composable @@ -34,3 +36,13 @@ fun EmptyMessage( ) } } + +@Composable +@Preview +fun EmptyMessagePreview(){ + FearlessAppTheme { + EmptyMessage( + message = R.string.choose_amount_error_balance + ) + } +} \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt new file mode 100644 index 0000000000..f82301af45 --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt @@ -0,0 +1,64 @@ +package jp.co.soramitsu.common.compose.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.valentinilk.shimmer.shimmer +import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme +import jp.co.soramitsu.common.compose.theme.greenText +import jp.co.soramitsu.common.compose.theme.warningOrange +import jp.co.soramitsu.common.compose.theme.warningYellow +import jp.co.soramitsu.common.compose.theme.white30 + +@Composable +fun ScoreStar(score: Int) { + val (starRes, color) = when (score) { + in 0..33 -> R.drawable.ic_score_star_empty to warningOrange + in 33..66 -> R.drawable.ic_score_star_half to warningYellow + in 66..100 -> R.drawable.ic_score_star_full to greenText + else -> R.drawable.ic_score_star_empty to white30 + } + + when { + score >= 0 -> { + Row { + Image(res = starRes, tint = color, modifier = Modifier.size(12.dp)) + MarginHorizontal(margin = 3.dp) + B2(text = score.toString(), color = color) + } + } + score == -1 -> { + // loading + Image(res = starRes, tint = color, modifier = Modifier + .size(12.dp) + .shimmer()) + } + score == -2 -> { + //error + Row { + Image(res = starRes, tint = color, modifier = Modifier.size(12.dp)) + MarginHorizontal(margin = 3.dp) + B2(text = "N/A", color = color) + } + } + } +} + +@Preview +@Composable +private fun ScoreStarPreview(){ + FearlessAppTheme { + Column { + ScoreStar(score = -1) + ScoreStar(score = -2) + ScoreStar(score = 0) + ScoreStar(score = 50) + ScoreStar(score = 100) + } + } +} \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt index 79c5724d54..46a96e3ae8 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt @@ -40,24 +40,30 @@ import jp.co.soramitsu.common.compose.theme.backgroundBlurColor import jp.co.soramitsu.common.compose.theme.colorAccent import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.utils.clickableWithNoIndication data class MainToolbarViewState( val title: String, - val homeIconState: ToolbarHomeIconState = ToolbarHomeIconState(), + val homeIconState: ToolbarHomeIconState = ToolbarHomeIconState.Navigation(R.drawable.ic_wallet), val selectorViewState: ChainSelectorViewState ) data class MainToolbarViewStateWithFilters( - val title: String, - val homeIconState: ToolbarHomeIconState = ToolbarHomeIconState(), - val selectorViewState: ChainSelectorViewStateWithFilters + val title: String?, + val homeIconState: ToolbarHomeIconState = ToolbarHomeIconState.Navigation(R.drawable.ic_wallet), + val selectorViewState: ChainSelectorViewStateWithFilters? ) -data class ToolbarHomeIconState( - val walletIcon: Drawable? = null, - @DrawableRes val navigationIcon: Int? = null, - val tint: Color = Color.Unspecified -) +sealed interface ToolbarHomeIconState{ + data class Wallet( + val walletIcon: Drawable, + val score: Int? = null, + ): ToolbarHomeIconState + data class Navigation( + @DrawableRes val navigationIcon: Int, + val tint: Color = Color.Unspecified + ): ToolbarHomeIconState +} data class MenuIconItem( @DrawableRes val icon: Int, @@ -153,6 +159,7 @@ fun MainToolbar( state: MainToolbarViewStateWithFilters, onChangeChainClick: () -> Unit, onNavigationClick: () -> Unit = {}, + onScoreClick: () -> Unit, menuItems: List? = null, modifier: Modifier = Modifier ) { @@ -170,7 +177,8 @@ fun MainToolbar( ) { ToolbarHomeIcon( state = state.homeIconState, - onClick = onNavigationClick + onClick = onNavigationClick, + onScoreClick = onScoreClick ) } Column( @@ -180,19 +188,30 @@ fun MainToolbar( .align(Alignment.Center), horizontalAlignment = CenterHorizontally ) { - Text( - text = state.title, - style = MaterialTheme.customTypography.header4, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) + if (state.title != null) { + Text( + text = state.title, + style = MaterialTheme.customTypography.header4, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } else { + Shimmer(Modifier.height(14.dp)) + } MarginVertical(margin = 4.dp) - ChainSelector( - selectorViewState = state.selectorViewState, - onChangeChainClick = onChangeChainClick - ) + if (state.selectorViewState != null) { + ChainSelector( + selectorViewState = state.selectorViewState, + onChangeChainClick = onChangeChainClick + ) + } else { + Shimmer( + Modifier + .height(12.dp) + .padding(horizontal = 20.dp)) + } } Row( verticalAlignment = CenterVertically, @@ -220,7 +239,7 @@ fun MainToolbar( @Composable fun MainToolbarShimmer( - homeIconState: ToolbarHomeIconState, + homeIconState: ToolbarHomeIconState? = null, menuItems: List? = null, modifier: Modifier = Modifier ) { @@ -236,12 +255,13 @@ fun MainToolbarShimmer( contentAlignment = Alignment.CenterStart, modifier = Modifier.weight(1f) ) { - homeIconState.navigationIcon?.let { + (homeIconState as? ToolbarHomeIconState.Navigation)?.let { IconButton( - painter = painterResource(id = it), + painter = painterResource(id = it.navigationIcon), tint = Color.Unspecified, onClick = {} ) + } } Column( @@ -283,17 +303,31 @@ fun MainToolbarShimmer( } @Composable -fun ToolbarHomeIcon(state: ToolbarHomeIconState, onClick: () -> Unit) { - when { - state.navigationIcon != null -> painterResource(id = state.navigationIcon) - state.walletIcon != null -> rememberAsyncImagePainter(model = state.walletIcon) - else -> null - }?.let { painter -> - IconButton( - painter = painter, - tint = state.tint, - onClick = onClick - ) +fun ToolbarHomeIcon(state: ToolbarHomeIconState, onClick: () -> Unit, onScoreClick: () -> Unit = {}) { + when (state) { + is ToolbarHomeIconState.Navigation -> { + IconButton( + painter = painterResource(id = state.navigationIcon), + tint = state.tint, + onClick = onClick + ) + } + + is ToolbarHomeIconState.Wallet -> { + Column(horizontalAlignment = CenterHorizontally) { + IconButton( + painter = rememberAsyncImagePainter(model = state.walletIcon), + onClick = onClick + ) + MarginVertical(margin = 6.dp) + + state.score?.let { + Box(modifier = Modifier.clickableWithNoIndication { onScoreClick() }) { + ScoreStar(score = it) + } + } + } + } } } @@ -328,15 +362,18 @@ fun Toolbar(state: ToolbarViewState, modifier: Modifier = Modifier, onNavigation verticalAlignment = CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - Box( - contentAlignment = CenterStart, - modifier = Modifier.weight(1f) - ) { - ToolbarHomeIcon( - state = ToolbarHomeIconState(navigationIcon = state.navigationIcon), - onClick = onNavigationClick - ) + state.navigationIcon?.let { navIcon -> + Box( + contentAlignment = CenterStart, + modifier = Modifier.weight(1f) + ) { + ToolbarHomeIcon( + state = ToolbarHomeIconState.Navigation(navigationIcon = navIcon), + onClick = onNavigationClick + ) + } } + Column( modifier = Modifier .fillMaxWidth() @@ -384,7 +421,7 @@ private fun MainToolbarPreview() { .padding(16.dp) ) { MainToolbarShimmer( - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_wallet), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_wallet), menuItems = listOf( MenuIconItem(icon = R.drawable.ic_scan, {}), MenuIconItem(icon = R.drawable.ic_search, {}) @@ -394,7 +431,7 @@ private fun MainToolbarPreview() { MainToolbar( state = MainToolbarViewState( title = "Fearless wallet very long wallet name", - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_wallet), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_wallet), selectorViewState = ChainSelectorViewState( selectedChainId = "id", selectedChainName = "Crust shadow parachain", diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt index fb23164513..44f56e40d3 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt @@ -39,7 +39,8 @@ data class WalletItemViewState( val title: String, val walletIcon: Any, val isSelected: Boolean, - val additionalMetadata: String = "" + val additionalMetadata: String = "", + val score: Int? = null ) @Composable @@ -48,6 +49,7 @@ fun WalletItem( onOptionsClick: ((WalletItemViewState) -> Unit)? = null, onSelected: (WalletItemViewState) -> Unit, onLongClick: (WalletItemViewState) -> Unit = {}, + onScoreClick: (WalletItemViewState) -> Unit = {}, modifier: Modifier = Modifier ) { val borderColor = if (state.isSelected) { @@ -120,6 +122,11 @@ fun WalletItem( } } Spacer(modifier = Modifier.weight(1f)) + state.score?.let { score -> + Box(modifier = Modifier.padding(9.dp).clickableWithNoIndication { onScoreClick(state) }) { + ScoreStar(score = score) + } + } onOptionsClick?.let { optionsAction -> Box( contentAlignment = Alignment.CenterEnd @@ -166,7 +173,8 @@ private fun WalletItemPreview() { title = walletTitle, walletIcon = R.drawable.ic_wallet, isSelected = isSelected, - changeBalanceViewState = changeBalanceViewState + changeBalanceViewState = changeBalanceViewState, + score = 50 ) Column { diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt b/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt index f29d76a0f7..cc07271609 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt @@ -73,6 +73,7 @@ val accountIconDark = Color(0xFF000000) val errorRed = Color(0xFFFF3B30) val warningOrange = Color(0xFFEE7700) val alertYellow = Color(0xFFEE7700) +val warningYellow = Color(0xFFFFD600) val transparent = Color(0xffffff) diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt index 7ca072658a..0291ed2c8d 100644 --- a/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisApi.kt @@ -7,5 +7,5 @@ interface NomisApi { @GET("wallet/{address}/score/") suspend fun getNomisScore( @Path("address") address: String - ): String + ): NomisResponse } \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisResponse.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisResponse.kt new file mode 100644 index 0000000000..b3055aacba --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/nomis/NomisResponse.kt @@ -0,0 +1,29 @@ +package jp.co.soramitsu.common.data.network.nomis + +import com.google.gson.annotations.SerializedName + +data class NomisResponse( + val data: NomisResponseData +) + +data class NomisResponseData( + val address: String, + val score: Double, + val stats: NomisStats +) + +data class NomisStats( + val nativeBalanceUSD: Double, + val holdTokensBalanceUSD: Double, + @SerializedName("walletAge") + val walletAgeInMonths: Long, + val totalTransactions: Long, + val totalRejectedTransactions: Long, + @SerializedName("averageTransactionTime") + val averageTransactionTimeInHours: Double, + @SerializedName("maxTransactionTime") + val maxTransactionTimeInHours: Double, + @SerializedName("minTransactionTime") + val minTransactionTimeInHours: Double, + val scoredAt: String +) diff --git a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt index c6720757a7..b17c4d83ef 100644 --- a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt +++ b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt @@ -75,15 +75,16 @@ class NetworkModule { @Singleton fun provideNomisHttpClient(): NomisApi { val builder = OkHttpClient.Builder() - .connectTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) - .writeTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) - .readTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) +// .connectTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) +// .writeTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) +// .readTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .callTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) .addInterceptor { - val r = it.request().newBuilder().apply { + val request = it.request().newBuilder().apply { addHeader("X-API-Key", "j9Us1Kxoo9fs3nD") addHeader("X-ClientId", "FCEB90FC-E3F9-4CF5-980E-A8111A3FFF31") }.build() - it.proceed(r) + it.proceed(request) } .retryOnConnectionFailure(true) diff --git a/common/src/main/res/drawable/ic_score_star_empty.xml b/common/src/main/res/drawable/ic_score_star_empty.xml new file mode 100644 index 0000000000..80bbc5196f --- /dev/null +++ b/common/src/main/res/drawable/ic_score_star_empty.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/common/src/main/res/drawable/ic_score_star_full.xml b/common/src/main/res/drawable/ic_score_star_full.xml new file mode 100644 index 0000000000..2b866550f8 --- /dev/null +++ b/common/src/main/res/drawable/ic_score_star_full.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/main/res/drawable/ic_score_star_half.xml b/common/src/main/res/drawable/ic_score_star_half.xml new file mode 100644 index 0000000000..f8926f80f6 --- /dev/null +++ b/common/src/main/res/drawable/ic_score_star_half.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index cd5f2ee784..539ec9473a 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -161,6 +161,7 @@ Saldo fiat Nama Popularitas + Perhatian Tersedia: %s Jaringan yang tersedia Dompet cadangan diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 4081b7217f..3f89386793 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -156,6 +156,7 @@ 現金残高 名前 人気 + 注意 利用可能: %s 利用可能なネットワーク ウォレットのバックアップ @@ -469,6 +470,26 @@ リンク 試験ネットワーク 言語 + 流動性提供のためのファーミング報酬 + 戦略的ボーナス年利 + 戦略的ボーナス年利 + 利用可能なプール + 流動性を確認 + 出力は推定です。価格が0.5%以上変動した場合、取引は元に戻されます。 + ネットワーク手数料はSORAシステムの成長と安定したパフォーマンスを確保するために使用されます。 + ネットワーク手数料 + プールの詳細 + プールトークンを削除すると、あなたのポジションが現在のレートで元のトークンに戻されます。プール内のシェアに比例します。受け取る金額には累積された手数料が含まれます。 + + 流動性を削除 + 流動性を削除 + %s を獲得 + 報酬の支払い先 + スリッページ + 流動性を供給 + 流動性を供給 + %s プール済み + ユーザープール 送信された支払いトランザクション アカウントを追加... トークンまたはネットワークで検索 diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 9c2cdb7660..a805fe996a 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -156,6 +156,7 @@ Saldo Fiat Nome Popularidade + Atenção Disponível: %s Faça backup da sua carteira Saldo diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index edc09f5d79..4423ecc02a 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -193,6 +193,12 @@ %d часов %d час + + %d месяц + %d месяца + %d месяцев + %d месяц + Импорт кошелька Важно Информация @@ -401,6 +407,26 @@ Ссылка Testnet Язык + Вознаграждение за предоставление ликвидности + Стратегический бонус APY + Стратегический бонус APY + Доступные пулы + Подтвердите ликвидность + Оценка результата. Если цена изменится более чем на 0.5%, ваша транзакция будет отменена + Комиссия сети используется для обеспечения роста и стабильной работы системы SORA. + Комиссия сети + Детали пула ликвидности + Удаление токенов из пула конвертирует вашу позицию обратно в исходные токены по текущему курсу, пропорционально вашей доле в пуле. Начисленные комиссии включены в получаемые вами суммы. + ПРИМЕЧАНИЕ + Убрать ликвидность + Убрать ликвидность + Заработать %s + Выплата вознаграждений в + Проскальзывание + Добавить ликвидность + Добавить ликвидность + Ваши %s добавлены в пул + Пулы пользователя Транзакция на выплату отправлена Добавить аккаунт... Поиск по токену diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 50de2725a1..33da037cff 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -160,6 +160,7 @@ Fiat dengesi İsim Popülerlik + Dikkat Mevcut: %s Mevcut ağlar Cüzdan yedekleme @@ -479,6 +480,26 @@ Bağlantı Testnet Dil + Likidite sağlanmasına yönelik tarım ödülü + Stratejik Bonus APY + Stratejik Bonus APY + Mevcut havuzlar + Likiditeyi Doğrula + Çıktı tahminidir. Fiyatın %0,5\'ten fazla değişmesi durumunda işleminiz geri alınacaktır. + Ağ ücreti, SORA sisteminin büyümesini ve istikrarlı performansını sağlamak için kullanılır. + Şebeke ücreti + Havuz detayları + Havuz tokenlarını kaldırmak, pozisyonunuzu havuzdaki payınızla orantılı olarak mevcut oranda temel tokenlara dönüştürür. Tahakkuk eden ücretler, aldığınız tutarlara dahildir. + NOT + Likiditeyi Kaldır + Likiditeyi Kaldır + %s Kazanın + Ödül Ödemesi + Kayma + Arz Likiditesinin + Arz Likiditesinin + %s Havuzunuz + Kullanıcı havuzları Ödeme işlemi gönderildi. Bir hesap ekle Tokene veya ağa göre ara diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index 85397407cd..c95433f4be 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -162,6 +162,7 @@ Số dư tiền pháp định Tên Phổ biến + Chú ý Có sẵn: %s Mạng có sẵn Sao lưu ví của bạn @@ -241,6 +242,7 @@ Tin nhắn Tối thiểu nhận được Module + Thêm Mạng của tôi Mạng Phí mạng @@ -302,6 +304,7 @@ Tổng cộng Giao dịch Dữ liệu thô giao dịch + Giao dịch đã gửi Giao dịch đã được gửi Chuyển nhượng: %s Thử lại @@ -485,6 +488,28 @@ Liên kết Testnet Ngôn ngữ + Phần thưởng canh tác để cung cấp thanh khoản + APY tiền thưởng chiến lược + APY tiền thưởng chiến lược + Pool có sẵn + Hiển thị chi tiết + Đầu tư tiền của bạn vào Pool\nthanh khoản và nhận phần thưởng + Xác nhận thanh khoản + Kết quả được ước tính. Nếu giá thay đổi hơn 0,5% thì giao dịch của bạn sẽ hoàn trả. + Phí mạng được sử dụng để đảm bảo sự tăng trưởng và hiệu suất ổn định của hệ thống SORA. + Phí mạng + Chi tiết Pool + Việc xóa pool token sẽ chuyển đổi vị trí của bạn trở lại thành token cơ bản ở mức giá hiện tại, tỷ lệ thuận với phần chia sẻ của bạn trong pool. Phí tích lũy được bao gồm trong số tiền bạn nhận được. + GHI CHÚ + Rút Thanh Khoản + Rút Thanh Khoản + Kiếm được %s + Thanh toán phần thưởng + Trượt giá + Nguồnn cung thanh khoản + Nguồnn cung thanh khoản + %s của bạn đã đóng góp + Pool người dùng Giao dịch thanh toán đã được gửi Thêm một tài khoản... Tìm kiếm theo tài sản @@ -719,11 +744,11 @@ Cấu hình lỗi thời Bạn đang cố gắng thực hiện chuyển khoản vào cùng một tài khoản. Hoạt động sẽ tính phí và không có ý nghĩa gì Địa chỉ này đã bị gắn cờ do một thực thể có liên quan đến một quốc gia đang bị trừng phạt. - Địa chỉ này đã bị gắn cờ do một thực thể có liên quan đến một quốc gia đang bị trừng phạt. Chúng tôi thực sự khuyên bạn không nên gửi %s tới tài khoản này. + Địa chỉ này đã bị gắn cờ do có liên quan đến một quốc gia đang bị trừng phạt. Chúng tôi thực sự khuyên bạn không nên gửi %s vào tài khoản này. Bổ sung: Chúng tôi thực sự khuyên bạn không nên gửi %s vào tài khoản này. Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. - Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. Chúng tôi thực sự khuyên bạn không nên gửi %s tới tài khoản này. + Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. Chúng tôi thực sự khuyên bạn không nên gửi {asset} tới tài khoản này. Quét mã từ người nhận Quét mã QR Tải lên từ thư viện @@ -1035,6 +1060,7 @@ Substrate keypair crypto type Substrate secret derivation path\n Bạn có thể quay lại trình duyệt của mình ngay bây giờ + Giao dịch của bạn đã được gửi thành công tới blockchain Hỗ trợ & Phản hồi Chuyển đổi node Tự động chọn node @@ -1116,7 +1142,7 @@ Số dư tối thiểu Xác nhận chuyển khoản Giao dịch chuyển tiền của bạn sẽ không thành công vì số tiền cuối cùng trên tài khoản đích sẽ ít hơn số dư tối thiểu. Hãy cố gắng tăng số lượng. - Chuyển khoản của bạn sẽ không thành công vì số tiền cuối cùng (%s) trên tài khoản đích sẽ nhỏ hơn số dư tối thiểu (%s). Vui lòng tăng thêm %s + Chuyển khoản của bạn sẽ không thành công vì số tiền cuối cùng (%1$s) trên tài khoản đích sẽ nhỏ hơn số dư tối thiểu (%2$s). Vui lòng tăng thêm %3$s Số dư Ethereum không đủ trong tài khoản của người nhận sẽ ngăn cản việc hoàn tất chuyển mã thông báo ERC20. Vui lòng đảm bảo người nhận có đủ Ethereum để tiến hành chuyển khoản. Chuyển khoản của bạn sẽ xóa tài khoản khỏi blockstore vì nó sẽ làm cho tổng số dư còn lại thấp hơn số dư tối thiểu. Chuyển khoản sẽ xóa tài khoản diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml index 113c1f43ea..44f75500de 100644 --- a/common/src/main/res/values-zh/strings.xml +++ b/common/src/main/res/values-zh/strings.xml @@ -91,6 +91,7 @@ 显示助记词短语 展示原始种子 如果你失去了对这个设备的访问权限,除非你进行备份,否则你的资金将会丢失! + 受阻 治理 流动性池 提名池 @@ -161,6 +162,7 @@ 法币余额 名称 人气 + 注意 可用:%s 可用网络 钱包备份 @@ -244,6 +246,9 @@ 网络 网络费用 网络管理 + + %d 网络 + 下一个 请勿截屏,这可能会被第三方恶意软件收集。 @@ -287,6 +292,7 @@ 跳过流程 排序按照 质押 + 开始 截止到 %s 剩余时间 跨链 @@ -297,6 +303,7 @@ 总共 交易 交易原始数据 + 交易已发送 交易已提交 可转让:%s 请再试一次 @@ -480,6 +487,26 @@ 链接 测试网 语言 + 提供流动性的农场奖励 + 战略奖励APY + 战略奖励APY + 可用的流动池 + 确认流动性 + 输出是估计的。如果价格变动超过0.5%,您的交易将会回滚。 + 网络费用用于确保 SORA 系统的增长和稳定性能。 + 网络费用 + 流动池详情 + 移除流动性池代币会按照您在池中所占份额的比例,将您的头寸转换回当前汇率下的基础代币。已累积的费用将包含在您收到的金额中。 + 注意 + 移除流动性 + 移除流动性 + 赚取%s + 奖励支付以 + 滑点 + 供应流动性 + 供应流动性 + 你提供流动性的%s + 用户流动池 支付交易已发送 添加一个账户... 按资产搜索 @@ -506,6 +533,7 @@ 添加账户 切换节点 添加一个账户 + 连接错误:无法连接到网络。请重试。 网络不可用 节点不可用 网络问题 @@ -713,11 +741,11 @@ 过时的配置 您正在尝试向同一账户进行转账。此操作将收取费用且没有任何意义。 此地址因与受制裁国家相关的实体有关而被标记。 - 由于与受制裁国家相关的实体,此地址已被标记。我们强烈建议您不要向此账户发送%s。 + 由于与受制裁国家相关的实体,此地址已被标记。我们强烈建议您不要向此账户发送%s。 附加: 我们强烈建议您不要向此账户发送%s。 此地址因涉嫌欺诈行为已被标记。 - 这个地址因涉嫌欺诈行为而被标记。我们强烈建议您不要向该账户转账%s。 + 此地址已被标记存在诈骗嫌疑。我们强烈建议您不要将{asset}发送到此账户。 从接收方扫描代码 二维码 从相册上传 @@ -747,7 +775,7 @@ 社交媒体 您尝试转账的金额不足以支付%s网络上的交易费用。尽管交易无法完成,您仍将在Sora网络上被收取费用。 目前,为了确保SORA网络的稳定性和安全性,跨链桥转账的最低金额为%s。感谢您的理解。 - 0欧元年度服务费 + 0欧元年服务费 申请被排除 某些国家的居民
无法申请SORA卡片。
查看名单]]>
启用卡片 @@ -1028,6 +1056,7 @@ Substrate密钥对加密类型 Substrate密钥派生路径 你现在可以返回到你的浏览器了 + 您的交易已成功发送至区块链。 支持与反馈 切换节点 自动选择节点 @@ -1066,6 +1095,8 @@ 此名称仅会显示给您,并且仅在您的移动设备上本地存储。 创建一个新的钱包 佣金 + 活跃提名人中的最低质押金额为%s。要获得奖励,您需要质押更多。 + 活跃提名人的最低质押 此账户尚未被网络选中参与当前时间段 未找到验证人 由于每个平行链的独特归属计划,我们的应用无法显示可认领的锁定代币数量。请注意,如果预计金额微不足道且与涉及的交易费用相当,发起认领可能是不切实际的。要全面了解您的锁定余额,请咨询Subscan区块浏览器。我们敦促您仔细评估信息,并根据自己的判断继续操作。 @@ -1107,6 +1138,7 @@ 最低余额 确认转账 您的转账将失败,因为目标账户上的最终金额将低于最低余额。请尝试增加金额。 + 您的转账将失败,因为目标账户的最终金额(%1$s)将低于最低余额(%2$s)。请增加%3$s的金额。 接收方账户中的以太币余额不足,导致无法完成ERC20代币转账。请确保接收方有足够的以太币来进行转账。 您的转账将从区块存储中移除该账户,因为这将使总余额低于最低余额要求。 转账将删除账户 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 7bde9d8712..a36a382be4 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -47,6 +47,19 @@ No account found Account option Accounts with a shared secret + Avg. transaction time + Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain + Unable to retrieve account score information. Please try again later. + Hold tokens USD + Max transaction time + Min transaction time + Native balance USD + Rejected transactions + Your score + Total transactions + Updated + Wallet age + Show wallet score %s account Add an account Accounts to export @@ -233,6 +246,10 @@ %d hour %d hours + + %d month + %d months + Import wallet Important Info @@ -244,6 +261,7 @@ Message Min received Module + More My networks Network Network fee @@ -490,6 +508,28 @@ Link Testnet Language + Farming reward for liquidity provision + Strategic Bonus APY + Strategic Bonus APY + Available pools + Show details + Invest your funds in Liquidity\npools and receive rewards + Confirm Liquidity + Output is estimated. If the price changes more than 0.5% your transaction will revert. + Network fee is used to ensure SORA system’s growth and stable performance. + Network fee + Pool details + Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive. + NOTE + Remove Liquidity + Remove Liquidity + Earn %s + Rewards Payout In + Slippage + Supply Liquidity + Supply Liquidity + Your %s Pooled + User pools Payout transaction sent Add an account... Search by asset diff --git a/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt b/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt index b93321504d..468b583d3f 100644 --- a/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt +++ b/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt @@ -46,7 +46,8 @@ fun chainOf( rank = null, isChainlinkProvider = false, supportNft = false, - isUsesAppId = false + isUsesAppId = false, + identityChain = null ) fun ChainLocal.nodeOf( diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt index 56c02ed207..b062263a5c 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt @@ -16,6 +16,7 @@ import jp.co.soramitsu.coredb.dao.AddressBookDao import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.dao.OperationDao import jp.co.soramitsu.coredb.dao.PhishingDao import jp.co.soramitsu.coredb.dao.SoraCardDao @@ -70,6 +71,8 @@ import jp.co.soramitsu.coredb.migrations.Migration_63_64 import jp.co.soramitsu.coredb.migrations.Migration_64_65 import jp.co.soramitsu.coredb.migrations.Migration_65_66 import jp.co.soramitsu.coredb.migrations.Migration_66_67 +import jp.co.soramitsu.coredb.migrations.Migration_67_68 +import jp.co.soramitsu.coredb.migrations.Migration_68_69 import jp.co.soramitsu.coredb.migrations.RemoveAccountForeignKeyFromAsset_17_18 import jp.co.soramitsu.coredb.migrations.RemoveLegacyData_35_36 import jp.co.soramitsu.coredb.migrations.RemoveStakingRewardsTable_22_23 @@ -78,6 +81,7 @@ import jp.co.soramitsu.coredb.model.AccountLocal import jp.co.soramitsu.coredb.model.AccountStakingLocal import jp.co.soramitsu.coredb.model.AddressBookContact import jp.co.soramitsu.coredb.model.AssetLocal +import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal import jp.co.soramitsu.coredb.model.OperationLocal import jp.co.soramitsu.coredb.model.PhishingLocal import jp.co.soramitsu.coredb.model.SoraCardInfoLocal @@ -95,7 +99,7 @@ import jp.co.soramitsu.coredb.model.chain.FavoriteChainLocal import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal @Database( - version = 67, + version = 69, entities = [ AccountLocal::class, AddressBookContact::class, @@ -116,7 +120,8 @@ import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal ChainAccountLocal::class, ChainExplorerLocal::class, SoraCardInfoLocal::class, - ChainTypesLocal::class + ChainTypesLocal::class, + NomisWalletScoreLocal::class ] ) @TypeConverters( @@ -183,6 +188,8 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(Migration_64_65) .addMigrations(Migration_65_66) .addMigrations(Migration_66_67) + .addMigrations(Migration_67_68) + .addMigrations(Migration_68_69) .build() } return instance!! @@ -212,4 +219,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun addressBookDao(): AddressBookDao abstract fun soraCardDao(): SoraCardDao + + abstract fun nomisScoresDao(): NomisScoresDao } diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt new file mode 100644 index 0000000000..d1ea0e91fe --- /dev/null +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt @@ -0,0 +1,26 @@ +package jp.co.soramitsu.coredb.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal +import kotlinx.coroutines.flow.Flow + +@Dao +interface NomisScoresDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(scores: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(scores: NomisWalletScoreLocal) + + @Query("SELECT * FROM nomis_wallet_score") + suspend fun getScores(): List + + @Query("SELECT * FROM nomis_wallet_score") + fun observeScores(): Flow> + + @Query("SELECT * FROM nomis_wallet_score WHERE metaId = :metaId") + fun observeScore(metaId: Long): Flow +} \ No newline at end of file diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt index 279430ba57..4d3e82aae7 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt @@ -5,6 +5,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import jp.co.soramitsu.common.data.secrets.v1.SecretStoreV1 import jp.co.soramitsu.common.data.secrets.v2.SecretStoreV2 import jp.co.soramitsu.coredb.AppDatabase @@ -14,13 +15,13 @@ import jp.co.soramitsu.coredb.dao.AddressBookDao import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.dao.OperationDao import jp.co.soramitsu.coredb.dao.PhishingDao import jp.co.soramitsu.coredb.dao.SoraCardDao import jp.co.soramitsu.coredb.dao.StakingTotalRewardDao import jp.co.soramitsu.coredb.dao.StorageDao import jp.co.soramitsu.coredb.dao.TokenPriceDao -import javax.inject.Singleton @InstallIn(SingletonComponent::class) @Module @@ -107,4 +108,10 @@ class DbModule { fun provideSoraCardDao(appDatabase: AppDatabase): SoraCardDao { return appDatabase.soraCardDao() } + + @Provides + @Singleton + fun provideNomisScoresDao(appDatabase: AppDatabase): NomisScoresDao { + return appDatabase.nomisScoresDao() + } } diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt index 2b49e3199a..96dcc518c9 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt @@ -3,6 +3,31 @@ package jp.co.soramitsu.coredb.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +val Migration_68_69 = object : Migration(68, 69) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `nomis_wallet_score` ( + `metaId` INTEGER NOT NULL, + `score` INTEGER NOT NULL, + `updated` INTEGER NOT NULL, + `nativeBalanceUsd` TEXT NOT NULL, + `holdTokensUsd` TEXT NOT NULL, + `walletAgeInMonths` INTEGER NOT NULL, + `totalTransactions` INTEGER NOT NULL, + `rejectedTransactions` INTEGER NOT NULL, + `avgTransactionTimeInHours` REAL NOT NULL, + `maxTransactionTimeInHours` REAL NOT NULL, + `minTransactionTimeInHours` REAL NOT NULL, + `scoredAt` STRING NOT NULL + PRIMARY KEY(`metaId`), + FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE + ) + """.trimIndent() + ) + } +} + val Migration_67_68 = object : Migration(67, 68) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("UPDATE meta_accounts SET initialized = 0") diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt index 315845f72e..5f612cd916 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/NomisWalletScoreLocal.kt @@ -15,7 +15,7 @@ import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal childColumns = ["metaId"], onDelete = ForeignKey.CASCADE ) - ], + ] ) data class NomisWalletScoreLocal( val metaId: Long, @@ -23,10 +23,43 @@ data class NomisWalletScoreLocal( val updated: Long, val nativeBalanceUsd: BigDecimal, val holdTokensUsd: BigDecimal, - val walletAge: Long, + val walletAgeInMonths: Long, val totalTransactions: Long, val rejectedTransactions: Long, - val avgTransactionTime: Long, - val maxTransactionTime: Long, - val minTransactionTime: Long -) \ No newline at end of file + val avgTransactionTimeInHours: Double, + val maxTransactionTimeInHours: Double, + val minTransactionTimeInHours: Double, + val scoredAt: String +) { + companion object { + fun loading(metaId: Long) = NomisWalletScoreLocal( + metaId = metaId, + score = -1, + updated = 0, + nativeBalanceUsd = BigDecimal.ZERO, + holdTokensUsd = BigDecimal.ZERO, + walletAgeInMonths = 0, + totalTransactions = 0, + rejectedTransactions = 0, + avgTransactionTimeInHours = 0.0, + maxTransactionTimeInHours = 0.0, + minTransactionTimeInHours = 0.0, + scoredAt = "" + ) + + fun error(metaId: Long) = NomisWalletScoreLocal( + metaId = metaId, + score = -2, + updated = 0, + nativeBalanceUsd = BigDecimal.ZERO, + holdTokensUsd = BigDecimal.ZERO, + walletAgeInMonths = 0, + totalTransactions = 0, + rejectedTransactions = 0, + avgTransactionTimeInHours = 0.0, + maxTransactionTimeInHours = 0.0, + minTransactionTimeInHours = 0.0, + scoredAt = "" + ) + } +} \ No newline at end of file diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt index cd580f06d6..bd91ec7c21 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt @@ -5,6 +5,7 @@ import jp.co.soramitsu.account.api.domain.model.Account import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.backup.domain.models.BackupAccountType import jp.co.soramitsu.common.data.secrets.v2.ChainAccountSecrets import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets @@ -162,4 +163,9 @@ interface AccountInteractor { suspend fun updateFavoriteChain(chainId: ChainId, isFavorite: Boolean, metaId: Long) suspend fun selectedLightMetaAccount(): LightMetaAccount fun observeSelectedMetaAccountFavoriteChains(): Flow> + + fun observeNomisScores(): Flow> + + fun observeCurrentAccountScore(): Flow + fun observeAccountScore(metaId: Long): Flow } diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt index e85c8b4cc8..6d89781740 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt @@ -5,6 +5,7 @@ import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccountOrdering +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.backup.domain.models.BackupAccountType import jp.co.soramitsu.common.data.secrets.v2.ChainAccountSecrets import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets @@ -188,4 +189,7 @@ interface AccountRepository { suspend fun getSelectedLightMetaAccount(): LightMetaAccount suspend fun getLightMetaAccount(metaId: Long): LightMetaAccount fun observeFavoriteChains(metaId: Long): Flow> + + fun observeNomisScores(): Flow> + fun observeNomisScore(metaId: Long): Flow } diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt new file mode 100644 index 0000000000..72da0e3693 --- /dev/null +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt @@ -0,0 +1,21 @@ +package jp.co.soramitsu.account.api.domain.model + +import java.math.BigDecimal + +data class NomisScoreData( + val metaId: Long, + val score: Int, + val updated: Long, + val nativeBalanceUsd: BigDecimal, + val holdTokensUsd: BigDecimal, + val walletAgeInMonths: Long, + val totalTransactions: Long, + val rejectedTransactions: Long, + val avgTransactionTimeInHours: Double, + val maxTransactionTimeInHours: Double, + val minTransactionTimeInHours: Double, + val scoredAt: Long? +){ + val isError = score == -2 + val isLoading = score == -1 +} diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt index 13cd096a4d..fdf42adad1 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt @@ -1,14 +1,19 @@ package jp.co.soramitsu.account.impl.data.mappers +import java.text.SimpleDateFormat +import java.util.Locale import jp.co.soramitsu.account.api.domain.model.Account import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.account.impl.presentation.node.model.NodeModel import jp.co.soramitsu.account.impl.presentation.view.advanced.encryption.model.CryptoTypeModel +import jp.co.soramitsu.common.data.network.nomis.NomisResponse import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.core.models.ChainNode import jp.co.soramitsu.core.models.CryptoType +import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal import jp.co.soramitsu.coredb.model.chain.ChainAccountLocal import jp.co.soramitsu.coredb.model.chain.FavoriteChainLocal import jp.co.soramitsu.coredb.model.chain.JoinedMetaAccountInfo @@ -149,3 +154,42 @@ fun mapChainAccountToAccount( position = 0 ) } + +fun NomisResponse.toLocal(metaId: Long): NomisWalletScoreLocal { + val score = (data.score * 100).toInt() + return NomisWalletScoreLocal( + metaId = metaId, + score = score, + updated = System.currentTimeMillis(), + nativeBalanceUsd = data.stats.nativeBalanceUSD.toBigDecimal(), + holdTokensUsd = data.stats.holdTokensBalanceUSD.toBigDecimal(), + walletAgeInMonths = data.stats.walletAgeInMonths, + totalTransactions = data.stats.totalTransactions, + rejectedTransactions = data.stats.totalRejectedTransactions, + avgTransactionTimeInHours = data.stats.averageTransactionTimeInHours, + maxTransactionTimeInHours = data.stats.maxTransactionTimeInHours, + minTransactionTimeInHours = data.stats.minTransactionTimeInHours, + scoredAt = data.stats.scoredAt + ) +} + +fun NomisWalletScoreLocal.toDomain(): NomisScoreData { + val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.getDefault()) +// sdf.timeZone = TimeZone.getTimeZone("UTC") + val scoredAtMillis = runCatching { formatter.parse(scoredAt)?.time }.getOrNull() + + return NomisScoreData( + metaId = metaId, + score = score, + updated = updated, + nativeBalanceUsd = nativeBalanceUsd, + holdTokensUsd = holdTokensUsd, + walletAgeInMonths = walletAgeInMonths, + totalTransactions = totalTransactions, + rejectedTransactions = rejectedTransactions, + avgTransactionTimeInHours = avgTransactionTimeInHours, + maxTransactionTimeInHours = maxTransactionTimeInHours, + minTransactionTimeInHours = minTransactionTimeInHours, + scoredAt = scoredAtMillis + ) +} \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt index 4ef4ca5169..fa795c4680 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt @@ -9,10 +9,11 @@ import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccountOrdering +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.account.api.domain.model.cryptoType import jp.co.soramitsu.account.api.domain.model.hasChainAccount -import jp.co.soramitsu.account.impl.data.mappers.mapMetaAccountLocalToMetaAccount +import jp.co.soramitsu.account.impl.data.mappers.toDomain import jp.co.soramitsu.account.impl.data.repository.datasource.AccountDataSource import jp.co.soramitsu.backup.domain.models.BackupAccountType import jp.co.soramitsu.common.data.Keypair @@ -31,16 +32,14 @@ import jp.co.soramitsu.core.crypto.mapEncryptionToCryptoType import jp.co.soramitsu.core.model.Language import jp.co.soramitsu.core.model.SecuritySource import jp.co.soramitsu.core.models.CryptoType -import jp.co.soramitsu.core.models.accountIdOf import jp.co.soramitsu.coredb.dao.AccountDao -import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.model.AccountLocal -import jp.co.soramitsu.coredb.model.AssetLocal +import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal import jp.co.soramitsu.coredb.model.chain.ChainAccountLocal import jp.co.soramitsu.coredb.model.chain.FavoriteChainLocal import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal -import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId @@ -62,8 +61,6 @@ import jp.co.soramitsu.shared_utils.runtime.AccountId import jp.co.soramitsu.shared_utils.scale.EncodableStruct import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.addressByte import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId -import jp.co.soramitsu.wallet.api.data.cache.AssetCache -import jp.co.soramitsu.wallet.impl.domain.model.Asset import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -87,9 +84,8 @@ class AccountRepositoryImpl( private val jsonSeedDecoder: JsonSeedDecoder, private val jsonSeedEncoder: JsonSeedEncoder, private val languagesHolder: LanguagesHolder, - private val chainRegistry: ChainRegistry, private val chainsRepository: ChainsRepository, - private val assetDao: AssetDao, + private val nomisScoresDao: NomisScoresDao, private val dispatcher: CoroutineDispatcher = Dispatchers.Default ) : AccountRepository { @@ -931,4 +927,16 @@ class AccountRepositoryImpl( } override fun observeFavoriteChains(metaId: Long) = accountDataSource.observeFavoriteChains(metaId).map { list -> list.associate { it.chainId to it.isFavorite } } + + override fun observeNomisScores(): Flow> { + return nomisScoresDao.observeScores().map { scores -> + scores.map(NomisWalletScoreLocal::toDomain) + } + } + + override fun observeNomisScore(metaId: Long): Flow { + return nomisScoresDao.observeScore(metaId).map { score -> + score.toDomain() + } + } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt index 3d68a6fbd9..767eecf2cf 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt @@ -43,6 +43,7 @@ import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.coredb.dao.AccountDao import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.dao.TokenPriceDao import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository @@ -76,9 +77,8 @@ class AccountFeatureModule { jsonSeedDecoder: JsonSeedDecoder, jsonSeedEncoder: JsonSeedEncoder, languagesHolder: LanguagesHolder, - chainRegistry: ChainRegistry, chainsRepository: ChainsRepository, - assetDao: AssetDao + nomisScoresDao: NomisScoresDao ): AccountRepository { return AccountRepositoryImpl( accountDataSource, @@ -88,9 +88,8 @@ class AccountFeatureModule { jsonSeedDecoder, jsonSeedEncoder, languagesHolder, - chainRegistry, chainsRepository, - assetDao + nomisScoresDao ) } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt index fb21a51fd4..aaf4590ab3 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt @@ -7,12 +7,15 @@ import jp.co.soramitsu.account.api.domain.model.Account import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccountOrdering +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.common.interfaces.FileProvider +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.core.model.Language import jp.co.soramitsu.core.models.CryptoType import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn @@ -327,4 +330,24 @@ class AccountInteractorImpl( accountRepository.observeFavoriteChains(it.id) }.flowOn(Dispatchers.IO) } + + override fun observeNomisScores(): Flow> { + return accountRepository.observeNomisScores() + .flowOn(context) + } + + override fun observeCurrentAccountScore(): Flow { + return selectedMetaAccountFlow().flatMapLatest { + accountRepository.observeNomisScore(it.id) + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun observeAccountScore(metaId: Long): Flow { + return flowOf { + accountRepository.getMetaAccount(metaId) + }.flatMapLatest { + accountRepository.observeNomisScore(it.id) + } + } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt index bc10d771ec..a16979df59 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt @@ -5,6 +5,8 @@ import java.math.BigInteger import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.account.impl.data.mappers.mapMetaAccountLocalToMetaAccount +import jp.co.soramitsu.account.impl.data.mappers.toLocal +import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.network.runtime.binding.AssetBalance import jp.co.soramitsu.common.data.network.runtime.binding.toAssetBalance import jp.co.soramitsu.common.utils.orZero @@ -13,7 +15,10 @@ import jp.co.soramitsu.core.models.ChainAssetType import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.model.AssetLocal +import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal +import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain @@ -30,9 +35,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.job import kotlinx.coroutines.joinAll @@ -40,6 +47,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull + private const val TAG = "WalletSyncService" class WalletSyncService( private val metaAccountDao: MetaAccountDao, @@ -47,6 +55,8 @@ class WalletSyncService( private val chainRegistry: ChainRegistry, private val remoteStorageSource: RemoteStorageSource, private val assetDao: AssetDao, + private val nomisApi: NomisApi, + private val nomisScoresDao: NomisScoresDao, dispatcher: CoroutineDispatcher = Dispatchers.Default, ) { companion object { @@ -61,11 +71,20 @@ class WalletSyncService( ) }) + private val nomisUpdateScope = + CoroutineScope(dispatcher + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> + Log.d( + TAG, + "Nomis scope error: $throwable" + ) + }) + private var syncJob: Job? = null fun start() { observeNotInitializedMetaAccounts() observeNotInitializedChainAccounts() + observeNomisScores() } private fun observeNotInitializedMetaAccounts() { @@ -527,4 +546,53 @@ class WalletSyncService( } }.flatten() + emptyAssets } + + private fun observeNomisScores() { + var syncJob: Job? = null + metaAccountDao.metaAccountsFlow() + .distinctUntilChangedBy { it.size } + .map { accounts -> + val existingScores = nomisScoresDao.getScores() + val currentTime = System.currentTimeMillis() + val twelveHoursMillis = 12 * 60 * 60 * 1000L + val existingScoresToUpdate = + existingScores.filter { currentTime - it.updated > twelveHoursMillis } + .map { it.metaId } + + val metaAccounts = accounts.asSequence() + .filter { it.ethereumAddress != null } + + val newAccounts = + metaAccounts.filter { it.id !in existingScores.map { score -> score.metaId } } + + val accountsToUpdate = accounts.asSequence() + .filter { it.id in existingScoresToUpdate } + + (newAccounts + accountsToUpdate).toSet() + } + .onEach { metaAccounts -> + syncJob?.cancel() + syncJob = nomisUpdateScope.launch { + syncNomisScores(*metaAccounts.toTypedArray()) + } + } + .launchIn(nomisUpdateScope) + } + + private suspend fun syncNomisScores(vararg metaAccount: MetaAccountLocal) { + return coroutineScope { + metaAccount.onEach { account -> + launch { + nomisScoresDao.insert(NomisWalletScoreLocal.loading(account.id)) + runCatching { + nomisApi.getNomisScore(account.ethereumAddress!!.toHexString(true)) + }.onSuccess { response -> + nomisScoresDao.insert(response.toLocal(account.id)) + }.onFailure { + nomisScoresDao.insert(NomisWalletScoreLocal.error(account.id)) + } + } + } + } + } } \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt new file mode 100644 index 0000000000..e86a8be995 --- /dev/null +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.account.impl.presentation.nomis_scoring + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.core.os.bundleOf +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.hilt.android.AndroidEntryPoint +import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment +import jp.co.soramitsu.common.compose.component.BottomSheetScreen + +@AndroidEntryPoint +class ScoreDetailsFragment : BaseComposeBottomSheetDialogFragment() { + + companion object { + fun getBundle(metaId: Long) = bundleOf(META_ACCOUNT_ID_KEY to metaId) + + const val META_ACCOUNT_ID_KEY = "meta_account_id" + } + + override val viewModel: ScoreDetailsViewModel by viewModels() + + @Composable + override fun Content(padding: PaddingValues) { + val state by viewModel.state.collectAsStateWithLifecycle() + + BottomSheetScreen { + ScoreDetailsContent(state, viewModel) + } + } +} \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt new file mode 100644 index 0000000000..d07f49ac76 --- /dev/null +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt @@ -0,0 +1,301 @@ +package jp.co.soramitsu.account.impl.presentation.nomis_scoring + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.valentinilk.shimmer.shimmer +import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.Address +import jp.co.soramitsu.common.compose.component.B2 +import jp.co.soramitsu.common.compose.component.ButtonViewState +import jp.co.soramitsu.common.compose.component.EmptyMessage +import jp.co.soramitsu.common.compose.component.FearlessCorneredShape +import jp.co.soramitsu.common.compose.component.H1 +import jp.co.soramitsu.common.compose.component.Image +import jp.co.soramitsu.common.compose.component.InfoTable +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.MenuIconItem +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.component.Toolbar +import jp.co.soramitsu.common.compose.component.ToolbarViewState +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme +import jp.co.soramitsu.common.compose.theme.black05 +import jp.co.soramitsu.common.compose.theme.black2 +import jp.co.soramitsu.common.compose.theme.greenText +import jp.co.soramitsu.common.compose.theme.warningOrange +import jp.co.soramitsu.common.compose.theme.warningYellow +import jp.co.soramitsu.common.compose.theme.white24 +import jp.co.soramitsu.common.compose.theme.white30 +import jp.co.soramitsu.common.data.network.runtime.binding.cast + +data class ScoreDetailsScreenState( + val address: String?, + val info: ScoreDetailsViewState +) + +sealed class ScoreDetailsViewState { + data object Loading : ScoreDetailsViewState() + data class Success(val data: ScoreInfoState) : ScoreDetailsViewState() + data object Error : ScoreDetailsViewState() +} + +data class ScoreInfoState( + val score: Int, + val updated: String, + val nativeBalanceUsd: String, + val holdTokensUsd: String, + val walletAge: String, + val totalTransactions: String, + val rejectedTransactions: String, + val avgTransactionTime: String, + val maxTransactionTime: String, + val minTransactionTime: String +) + +interface ScoreDetailsScreenCallback { + fun onBackClicked() + fun onCloseClicked() + fun onCopyAddressClicked() +} + +@Composable +fun ScoreDetailsContent( + state: ScoreDetailsScreenState, + callback: ScoreDetailsScreenCallback +) { + val verticalScrollModifier = if(state.info is ScoreDetailsViewState.Success) Modifier.verticalScroll(rememberScrollState()) else Modifier + Column(modifier = verticalScrollModifier + .fillMaxSize() + ) { + Toolbar( + state = ToolbarViewState( + stringResource(id = R.string.account_stats_title), + R.drawable.ic_arrow_back_24dp, + listOf( + MenuIconItem( + icon = R.drawable.ic_cross_24, + onClick = callback::onCloseClicked + ) + ) + ), + onNavigationClick = callback::onBackClicked + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MarginVertical(margin = 24.dp) + when (state.info) { + is ScoreDetailsViewState.Error -> ScoreBar(-2) + ScoreDetailsViewState.Loading -> ScoreBar(-1) + is ScoreDetailsViewState.Success -> ScoreBar(state.info.data.score) + } + + MarginVertical(margin = 16.dp) + B2( + text = stringResource(id = R.string.account_stats_description_text), + textAlign = TextAlign.Center, + color = black2 + ) + MarginVertical(margin = 6.dp) + state.info.takeIf { it is ScoreDetailsViewState.Success } + ?.cast()?.let { + H1(text = it.data.score.toString()) + } + MarginVertical(margin = 8.dp) + state.address?.let { Address(address = it, onClick = callback::onCopyAddressClicked) } + MarginVertical(margin = 16.dp) + + when (val info = state.info) { + is ScoreDetailsViewState.Error -> Error() + ScoreDetailsViewState.Loading -> ScoresInfoTable(null) + is ScoreDetailsViewState.Success -> ScoresInfoTable(state = info.data) + } + MarginVertical(margin = 16.dp) + AccentButton( + state = ButtonViewState(text = stringResource(id = R.string.common_close)), + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + onClick = callback::onCloseClicked + ) + MarginVertical(margin = 16.dp) + } + } +} + +@Composable +private fun Error() { + Surface( + modifier = Modifier + .fillMaxSize() + .border(1.dp, color = white24, shape = FearlessCorneredShape()), + shape = FearlessCorneredShape(), + color = black05 + ) { + Box(modifier = Modifier.padding(vertical = 64.dp)) { + + EmptyMessage( + message = R.string.account_stats_error_message, + modifier = Modifier.align(Alignment.Center) + ) + } + } + +} + +@Composable +private fun ScoresInfoTable(state: ScoreInfoState?) { + InfoTable( + items = listOf( + TitleValueViewState( + title = stringResource(id = R.string.account_stats_updated_title), + value = state?.updated + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_native_balance_usd_title), + value = state?.nativeBalanceUsd + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_hold_tokens_usd_title), + value = state?.holdTokensUsd + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_wallet_age_title), + value = state?.walletAge + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_total_transactions_title), + value = state?.totalTransactions + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_rejected_transactions_title), + value = state?.rejectedTransactions + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_avg_transaction_time_title), + value = state?.avgTransactionTime + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_max_transaction_time_title), + value = state?.maxTransactionTime + ), + TitleValueViewState( + title = stringResource(id = R.string.account_stats_min_transaction_time_title), + value = state?.minTransactionTime + ) + ) + ) +} + +@Composable +fun ScoreBar(score: Int, modifier: Modifier = Modifier) { + val color = when (score) { + in 0..33 -> warningOrange + in 33..66 -> warningYellow + in 66..100 -> greenText + else -> white30 + } + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + when { + score >= 0 -> { + val fullStars = score / 20 + val halfStar = if (score % 20 >= 5) 1 else 0 + val emptyStars = 5 - fullStars - halfStar + + repeat(fullStars) { + Image( + res = R.drawable.ic_score_star_full, + tint = color, + modifier = Modifier.size(30.dp) + ) + } + repeat(halfStar) { + Image( + res = R.drawable.ic_score_star_half, + tint = color, + modifier = Modifier.size(30.dp) + ) + } + repeat(emptyStars) { + Image( + res = R.drawable.ic_score_star_empty, + tint = color, + modifier = Modifier.size(30.dp) + ) + } + } + + score == -1 -> { + repeat(5) { + Image( + res = R.drawable.ic_score_star_empty, + tint = color, + modifier = Modifier + .size(30.dp) + .shimmer() + ) + } + } + + score == -2 -> { + repeat(5) { + Image( + res = R.drawable.ic_score_star_empty, + tint = color, + modifier = Modifier.size(30.dp) + ) + } + } + } + } +} + +@Composable +@Preview +private fun ScoreDetailsScreenPreview() { + FearlessAppTheme { + val info = ScoreInfoState( + score = 55, + updated = "Jan 1, 2024", + nativeBalanceUsd = "\$1,337.69", + holdTokensUsd = "\$1,337.69", + walletAge = "1 year", + totalTransactions = "1337", + rejectedTransactions = "40", + avgTransactionTime = "95 hours", + maxTransactionTime = "30 hours", + minTransactionTime = "1 hours" + ) + val successState = ScoreDetailsViewState.Success(info) + val state = ScoreDetailsScreenState( + "Blue Bird 0x23f4g34nign234ij134f0134ifm13i4f134f", + info = ScoreDetailsViewState.Error + ) + ScoreDetailsContent(state, object : ScoreDetailsScreenCallback { + override fun onBackClicked() = Unit + override fun onCloseClicked() = Unit + override fun onCopyAddressClicked() = Unit + }) + } +} \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt new file mode 100644 index 0000000000..5648f1f63e --- /dev/null +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt @@ -0,0 +1,105 @@ +package jp.co.soramitsu.account.impl.presentation.nomis_scoring + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.impl.presentation.AccountRouter +import jp.co.soramitsu.common.base.BaseViewModel +import jp.co.soramitsu.common.resources.ClipboardManager +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.feature_account_impl.R +import jp.co.soramitsu.shared_utils.extensions.toHexString +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +@HiltViewModel +class ScoreDetailsViewModel @Inject constructor( + private val router: AccountRouter, + private val accountInteractor: AccountInteractor, + private val clipboardManager: ClipboardManager, + private val resourceManager: ResourceManager, + private val savedStateHandle: SavedStateHandle +) : BaseViewModel(), ScoreDetailsScreenCallback { + + val state: MutableStateFlow = MutableStateFlow( + ScoreDetailsScreenState( + address = "", + info = ScoreDetailsViewState.Loading + ) + ) + + private val rawAddress: MutableStateFlow = MutableStateFlow("") + + init { + val metaAccountId = requireNotNull(savedStateHandle.get(ScoreDetailsFragment.META_ACCOUNT_ID_KEY)) + accountInteractor.observeAccountScore(metaAccountId) + .onEach { nomisData -> + val newInfoState = when (nomisData.score) { + -1 -> ScoreDetailsViewState.Loading + -2 -> ScoreDetailsViewState.Error + else -> { + val scoredAt = nomisData.scoredAt?.let { + val formatter = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) + val date = Date(it) + formatter.format(date) + } ?: "0" + + ScoreDetailsViewState.Success( + data = ScoreInfoState( + score = nomisData.score, + updated = scoredAt, + nativeBalanceUsd = nomisData.nativeBalanceUsd.formatFiat(null), + holdTokensUsd = nomisData.holdTokensUsd.formatFiat(null), + walletAge = resourceManager.getQuantityString(R.plurals.common_months_format, nomisData.walletAgeInMonths.toInt(), nomisData.walletAgeInMonths.toInt()), + totalTransactions = nomisData.totalTransactions.toString(), + rejectedTransactions = nomisData.rejectedTransactions.toString(), + avgTransactionTime = resourceManager.getQuantityString(R.plurals.common_hours_format, nomisData.avgTransactionTimeInHours.toInt(), nomisData.avgTransactionTimeInHours.toInt()), + maxTransactionTime = resourceManager.getQuantityString(R.plurals.common_hours_format, nomisData.maxTransactionTimeInHours.toInt(), nomisData.maxTransactionTimeInHours.toInt()), + minTransactionTime = resourceManager.getQuantityString(R.plurals.common_hours_format, nomisData.minTransactionTimeInHours.toInt(), nomisData.minTransactionTimeInHours.toInt()), + ) + ) + } + } + state.update { prevState -> prevState.copy(info = newInfoState) } + } + .catch { + state.update { prevState -> prevState.copy(info = ScoreDetailsViewState.Error) } + showError(it) + } + .launchIn(viewModelScope) + + viewModelScope.launch { + val metaAccount = accountInteractor.selectedMetaAccount() + val address = metaAccount.ethereumAddress?.toHexString(withPrefix = true) + if (address != null) { + rawAddress.value = address + state.update { prevState -> prevState.copy(address = "${metaAccount.name} $address") } + } else { + state.update { prevState -> prevState.copy(info = ScoreDetailsViewState.Error) } + } + } + } + + override fun onBackClicked() { + router.back() + } + + override fun onCloseClicked() { + router.back() + } + + override fun onCopyAddressClicked() { + clipboardManager.addToClipboard(rawAddress.value) + showMessage(resourceManager.getString(R.string.common_copied)) + } +} \ No newline at end of file diff --git a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/NFTFlowFragment.kt b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/NFTFlowFragment.kt index 77a5e3c1fc..48c0d2aa15 100644 --- a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/NFTFlowFragment.kt +++ b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/NFTFlowFragment.kt @@ -198,7 +198,7 @@ class NFTFlowFragment : BaseComposeBottomSheetDialogFragment() is LoadingState.Loading> -> MainToolbarShimmer( - homeIconState = ToolbarHomeIconState() + homeIconState = ToolbarHomeIconState.Navigation(jp.co.soramitsu.feature_wallet_impl.R.drawable.ic_arrow_back_24dp) ) } diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt index 9e20c2eee7..6cef5f3930 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt @@ -107,5 +107,4 @@ interface WalletRepository { suspend fun estimateClaimRewardsFee(chainId: ChainId): BigInteger suspend fun claimRewards(chain: IChain, accountId: AccountId): Result suspend fun updateAssetsHidden(state: List) - suspend fun testNomisVitalikScore(): Result } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt index a0f78876ce..cc35a1b208 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt @@ -10,7 +10,6 @@ import jp.co.soramitsu.common.data.network.HttpExceptionHandler import jp.co.soramitsu.common.data.network.coingecko.CoingeckoApi import jp.co.soramitsu.common.data.network.config.AppConfigRemote import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher -import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.network.runtime.binding.UseCaseBinding import jp.co.soramitsu.common.data.network.runtime.binding.bindNumber import jp.co.soramitsu.common.data.network.runtime.binding.bindString @@ -27,7 +26,6 @@ import jp.co.soramitsu.common.utils.requireValue import jp.co.soramitsu.common.utils.tokens import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.models.Asset.PriceProvider -import jp.co.soramitsu.core.models.Asset.PriceProviderType.Chainlink import jp.co.soramitsu.core.models.IChain import jp.co.soramitsu.core.runtime.storage.returnType import jp.co.soramitsu.core.utils.utilityAsset @@ -102,8 +100,7 @@ class WalletRepositoryImpl( private val accountRepository: AccountRepository, private val chainsRepository: ChainsRepository, private val extrinsicService: ExtrinsicService, - private val remoteStorageSource: StorageDataSource, - private val nomisApi: NomisApi + private val remoteStorageSource: StorageDataSource ) : WalletRepository, UpdatesProviderUi by updatesMixin { companion object { @@ -551,10 +548,6 @@ class WalletRepositoryImpl( return kotlin.runCatching { remoteConfigFetcher.getAppConfig() } } - override suspend fun testNomisVitalikScore(): Result { - return kotlin.runCatching { nomisApi.getNomisScore("0xd8da6bf26964af9d7eed9e03e53415d37aa96045") } - } - override suspend fun getControllerAccount(chainId: ChainId, accountId: AccountId): AccountId? { return substrateSource.getControllerAccount(chainId, accountId) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt index 6c5a482226..acdb486929 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt @@ -36,6 +36,7 @@ import jp.co.soramitsu.coredb.dao.AddressBookDao import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.coredb.dao.MetaAccountDao +import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.dao.OperationDao import jp.co.soramitsu.coredb.dao.PhishingDao import jp.co.soramitsu.coredb.dao.TokenPriceDao @@ -178,8 +179,7 @@ class WalletFeatureModule { chainsRepository: ChainsRepository, extrinsicService: ExtrinsicService, @Named(REMOTE_STORAGE_SOURCE) - remoteStorageSource: StorageDataSource, - nomisApi: NomisApi + remoteStorageSource: StorageDataSource ): WalletRepository = WalletRepositoryImpl( substrateSource, ethereumRemoteSource, @@ -197,8 +197,7 @@ class WalletFeatureModule { accountRepository, chainsRepository, extrinsicService, - remoteStorageSource, - nomisApi + remoteStorageSource ) @Provides @@ -209,13 +208,17 @@ class WalletFeatureModule { chainRegistry: ChainRegistry, remoteStorageSource: RemoteStorageSource, assetDao: AssetDao, + nomisApi: NomisApi, + nomisScoresDao: NomisScoresDao ): WalletSyncService { return WalletSyncService( metaAccountDao, chainsRepository, chainRegistry, remoteStorageSource, - assetDao + assetDao, + nomisApi, + nomisScoresDao ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt index e4ac7a2664..833ac786c3 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt @@ -195,4 +195,6 @@ interface WalletRouter : SecureRouter, WalletRouterApi { fun openManageAssets() fun openServiceScreen() + + fun openScoreDetailsScreen(metaId: Long) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsScreen.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsScreen.kt index fdf748f1c3..d13c7e3600 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsScreen.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsScreen.kt @@ -68,7 +68,7 @@ fun AssetDetailsToolbar( when(state) { is LoadingState.Loading -> { MainToolbarShimmer( - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_arrow_back_24dp), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_arrow_back_24dp), ) } is LoadingState.Loaded -> { @@ -84,7 +84,7 @@ fun AssetDetailsToolbar( .align(Alignment.CenterStart) ) { ToolbarHomeIcon( - state = ToolbarHomeIconState(navigationIcon = state.data.homeIconState.navigationIcon), + state = state.data.homeIconState, onClick = callback::onNavigationBack ) } @@ -121,7 +121,7 @@ private fun AssetDetailsToolbarPreview() { state = LoadingState.Loaded( MainToolbarViewState( title = "MyWallet", - homeIconState = ToolbarHomeIconState( + homeIconState = ToolbarHomeIconState.Navigation( navigationIcon = R.drawable.ic_arrow_back_24dp ), selectorViewState = ChainSelectorViewState( diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsViewModel.kt index f97f509872..5c6667d876 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetDetails/AssetDetailsViewModel.kt @@ -18,7 +18,6 @@ import jp.co.soramitsu.common.compose.component.MainToolbarViewState import jp.co.soramitsu.common.compose.component.MultiToggleButtonState import jp.co.soramitsu.common.compose.component.NetworkIssueType import jp.co.soramitsu.common.compose.component.ToolbarHomeIconState -import jp.co.soramitsu.common.domain.model.NetworkIssue import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate @@ -139,7 +138,7 @@ class AssetDetailsViewModel @Inject constructor( LoadingState.Loaded( MainToolbarViewState( title = selectedMetaAccount.name, - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_arrow_back_24dp), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_arrow_back_24dp), selectorViewState = ChainSelectorViewState() ) ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailFragment.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailFragment.kt index 9894078303..b7713d3bad 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailFragment.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailFragment.kt @@ -105,7 +105,7 @@ class BalanceDetailFragment : BaseComposeFragment() { when (toolbarState) { is LoadingState.Loading -> { MainToolbarShimmer( - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_arrow_back_24dp), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_arrow_back_24dp), menuItems = listOf( MenuIconItem(icon = R.drawable.ic_dots_horizontal_24, {}) ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt index 6e92472fe4..14d1b41194 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.wallet.impl.presentation.balance.detail -import android.util.Log import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData @@ -180,7 +179,7 @@ class BalanceDetailViewModel @Inject constructor( LoadingState.Loaded( MainToolbarViewState( title = interactor.getSelectedMetaAccount().name, - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_arrow_back_24dp), + homeIconState = ToolbarHomeIconState.Navigation(navigationIcon = R.drawable.ic_arrow_back_24dp), selectorViewState = ChainSelectorViewState(selectedChain?.title, selectedChain?.id) ) ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListFragment.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListFragment.kt index 751798c976..faa575fb80 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListFragment.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListFragment.kt @@ -28,13 +28,9 @@ import jp.co.soramitsu.common.PLAY_MARKET_APP_URI import jp.co.soramitsu.common.PLAY_MARKET_BROWSER_URI import jp.co.soramitsu.common.base.BaseComposeFragment import jp.co.soramitsu.common.compose.component.MainToolbar -import jp.co.soramitsu.common.compose.component.MainToolbarShimmer -import jp.co.soramitsu.common.compose.component.MainToolbarViewStateWithFilters import jp.co.soramitsu.common.compose.component.MenuIconItem -import jp.co.soramitsu.common.compose.component.ToolbarHomeIconState import jp.co.soramitsu.common.data.network.coingecko.FiatCurrency import jp.co.soramitsu.common.presentation.FiatCurrenciesChooserBottomSheetDialog -import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.presentation.askPermissionsSafely import jp.co.soramitsu.common.scan.ScanTextContract import jp.co.soramitsu.common.scan.ScannerActivity @@ -88,35 +84,22 @@ class BalanceListFragment : BaseComposeFragment() { Modifier } Column(modifier = toolbarModifier) { - when (toolbarState) { - is LoadingState.Loading -> { - MainToolbarShimmer( - homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_wallet), - menuItems = listOf( - MenuIconItem(icon = R.drawable.ic_scan) {}, - MenuIconItem(icon = R.drawable.ic_search) {} - ) + MainToolbar( + state = toolbarState, + menuItems = listOf( + MenuIconItem( + icon = R.drawable.ic_scan, + onClick = ::requestCameraPermission + ), + MenuIconItem( + icon = R.drawable.ic_search, + onClick = viewModel::openSearchAssets ) - } - - is LoadingState.Loaded -> { - MainToolbar( - state = (toolbarState as LoadingState.Loaded).data, - menuItems = listOf( - MenuIconItem( - icon = R.drawable.ic_scan, - onClick = ::requestCameraPermission - ), - MenuIconItem( - icon = R.drawable.ic_search, - onClick = viewModel::openSearchAssets - ) - ), - onChangeChainClick = viewModel::openSelectChain, - onNavigationClick = viewModel::openWalletSelector - ) - } - } + ), + onChangeChainClick = viewModel::openSelectChain, + onNavigationClick = viewModel::openWalletSelector, + onScoreClick = viewModel::onScoreClick + ) } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index a29e3e7e1f..cfc258fbf8 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -43,7 +43,6 @@ import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.domain.model.NetworkIssueType import jp.co.soramitsu.common.mixin.api.UpdatesMixin import jp.co.soramitsu.common.mixin.api.UpdatesProviderUi -import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.resources.ClipboardManager import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.Event @@ -416,6 +415,50 @@ class BalanceListViewModel @Inject constructor( .stateIn(viewModelScope, SharingStarted.Eagerly, initialValue = null) } + private fun observeToolbarStates() { + currentAddressModelFlow().onEach { addressModel -> + toolbarState.update { prevState -> + val newWalletIconState = when(prevState.homeIconState) { + is ToolbarHomeIconState.Navigation -> ToolbarHomeIconState.Wallet(walletIcon = addressModel.image) + is ToolbarHomeIconState.Wallet -> (prevState.homeIconState as ToolbarHomeIconState.Wallet).copy(walletIcon = addressModel.image) + } + prevState.copy( + title = addressModel.nameOrAddress, + homeIconState = newWalletIconState, + ) + } + }.launchIn(viewModelScope) + + combine( + interactor.observeSelectedAccountChainSelectFilter(), + selectedChainItemFlow + ) { filter, chain -> + toolbarState.update { prevState -> + prevState.copy( + selectorViewState = ChainSelectorViewStateWithFilters( + selectedChainName = chain?.title, + selectedChainId = chain?.id, + selectedChainImageUrl = chain?.imageUrl, + filterApplied = ChainSelectorViewStateWithFilters.Filter.entries.find { + it.name == filter + } ?: ChainSelectorViewStateWithFilters.Filter.All + ) + ) + } + }.launchIn(viewModelScope) + + accountInteractor.observeCurrentAccountScore() + .onEach { score -> + toolbarState.update { prevState -> + val newWalletIconState = (prevState.homeIconState as? ToolbarHomeIconState.Wallet)?.copy(score = score.score) + newWalletIconState?.let { + prevState.copy(homeIconState = newWalletIconState) + } ?: prevState + } + } + .launchIn(viewModelScope) + } + private fun observeNetworkIssues() { combine( currentAssetsFlow, @@ -557,33 +600,11 @@ class BalanceListViewModel @Inject constructor( }.launchIn(this) } - val toolbarState = combine( - currentAddressModelFlow(), - interactor.observeSelectedAccountChainSelectFilter(), - selectedChainItemFlow - ) { addressModel, filter, chain -> - LoadingState.Loaded( - MainToolbarViewStateWithFilters( - title = addressModel.nameOrAddress, - homeIconState = ToolbarHomeIconState(walletIcon = addressModel.image), - selectorViewState = ChainSelectorViewStateWithFilters( - selectedChainName = chain?.title, - selectedChainId = chain?.id, - selectedChainImageUrl = chain?.imageUrl, - filterApplied = ChainSelectorViewStateWithFilters.Filter.entries.find { - it.name == filter - } ?: ChainSelectorViewStateWithFilters.Filter.All - ) - ) - ) - }.stateIn( - scope = this, - started = SharingStarted.Eagerly, - initialValue = LoadingState.Loading() - ) + val toolbarState: MutableStateFlow = MutableStateFlow(MainToolbarViewStateWithFilters(title = null, selectorViewState = null)) init { subscribeScreenState() + observeToolbarStates() observeNetworkIssues() observeFiatSymbolChange() sync() @@ -939,4 +960,11 @@ class BalanceListViewModel @Inject constructor( fun onServiceButtonClick() { router.openServiceScreen() } + + fun onScoreClick() { + viewModelScope.launch { + val currentAccount = currentMetaAccountFlow.first() + router.openScoreDetailsScreen(currentAccount.id) + } + } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletContent.kt index 736af1c919..a772dc24f8 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletContent.kt @@ -35,7 +35,8 @@ fun SelectWalletContent( onWalletOptionsClick: (WalletItemViewState) -> Unit, addNewWallet: () -> Unit, importWallet: () -> Unit, - onBackClicked: () -> Unit + onBackClicked: () -> Unit, + onScoreClick: (WalletItemViewState) -> Unit ) { BottomSheetScreen { Column( @@ -61,7 +62,8 @@ fun SelectWalletContent( WalletItem( state = walletItemState, onOptionsClick = onWalletOptionsClick, - onSelected = onWalletSelected + onSelected = onWalletSelected, + onScoreClick = onScoreClick ) } item { @@ -126,7 +128,8 @@ private fun SelectWalletScreenPreview() { addNewWallet = {}, importWallet = {}, onBackClicked = {}, - onWalletOptionsClick = {} + onWalletOptionsClick = {}, + onScoreClick = {} ) } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletFragment.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletFragment.kt index 8be99911f4..bcb5055e0d 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletFragment.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletFragment.kt @@ -54,7 +54,8 @@ class SelectWalletFragment : BaseComposeBottomSheetDialogFragment walletItemsFlow.update { @@ -69,11 +70,19 @@ class SelectWalletViewModel @Inject constructor( } } } - .onEach { - walletItemsFlow.update { prevList -> - prevList.map { prevState -> - val balanceModel = getTotalBalance(prevState.id) - prevState.copy( + .onEach { accounts -> + accounts.forEach { observeTotalBalance(it.id) } + observeScores() + } + .launchIn(viewModelScope) + } + + private fun observeTotalBalance(metaId: Long) { + getTotalBalance.observe(metaId).onEach { balanceModel -> + walletItemsFlow.update { + it.map { state -> + if(state.id == metaId) { + state.copy( balance = balanceModel.balance.formatFiat(balanceModel.fiatSymbol), changeBalanceViewState = ChangeBalanceViewState( percentChange = balanceModel.rateChange?.formatAsChange().orEmpty(), @@ -81,6 +90,21 @@ class SelectWalletViewModel @Inject constructor( .formatFiat(balanceModel.fiatSymbol) ) ) + } else { + state + } + } + } + }.launchIn(viewModelScope) + } + + private fun observeScores() { + accountInteractor.observeNomisScores() + .onEach { scores -> + walletItemsFlow.update { oldStates -> + oldStates.map { state -> + val score = scores.find { it.metaId == state.id } + score?.let { state.copy(score = score.score) } ?: state } } } @@ -196,4 +220,8 @@ class SelectWalletViewModel @Inject constructor( } } } + + fun onScoreClick(state: WalletItemViewState) { + router.openScoreDetailsScreen(state.id) + } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt index 32c9046c44..f0f1d90c36 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.impl.presentation.account.mixin.api.AccountListingMixin import jp.co.soramitsu.common.address.AddressIconGenerator @@ -11,8 +12,6 @@ import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.ChangeBalanceViewState import jp.co.soramitsu.common.compose.component.WalletItemViewState import jp.co.soramitsu.common.compose.component.WalletSelectorViewState -import jp.co.soramitsu.common.mixin.api.UpdatesMixin -import jp.co.soramitsu.common.mixin.api.UpdatesProviderUi import jp.co.soramitsu.common.navigation.payload.WalletSelectorPayload import jp.co.soramitsu.common.utils.formatAsChange import jp.co.soramitsu.common.utils.formatFiat @@ -21,6 +20,7 @@ import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -32,10 +32,10 @@ import kotlinx.coroutines.launch class WalletSelectorViewModel @Inject constructor( accountListingMixin: AccountListingMixin, private val router: WalletRouter, - private val updatesMixin: UpdatesMixin, private val totalBalanceUseCase: TotalBalanceUseCase, + private val accountInteractor: AccountInteractor, savedStateHandle: SavedStateHandle -) : BaseViewModel(), UpdatesProviderUi by updatesMixin { +) : BaseViewModel() { private val tag = savedStateHandle.get(WalletSelectorFragment.TAG_ARGUMENT_KEY)!! private val selectedWalletId = @@ -47,9 +47,10 @@ class WalletSelectorViewModel @Inject constructor( init { accountListingMixin.accountsFlow(AddressIconGenerator.SIZE_BIG) + .distinctUntilChanged() .inBackground() .onEach { newList -> - walletItemsFlow.value = + walletItemsFlow.update { newList.map { WalletItemViewState( id = it.id, @@ -60,13 +61,21 @@ class WalletSelectorViewModel @Inject constructor( changeBalanceViewState = null ) } - + } + } + .onEach { accounts -> + accounts.forEach { observeTotalBalance(it.id) } + observeScores() } - .onEach { - walletItemsFlow.update { prevList -> - prevList.map { prevState -> - val balanceModel = totalBalanceUseCase(prevState.id) - prevState.copy( + .launchIn(viewModelScope) + } + + private fun observeTotalBalance(metaId: Long) { + totalBalanceUseCase.observe(metaId).onEach { balanceModel -> + walletItemsFlow.update { + it.map { state -> + if(state.id == metaId) { + state.copy( balance = balanceModel.balance.formatFiat(balanceModel.fiatSymbol), changeBalanceViewState = ChangeBalanceViewState( percentChange = balanceModel.rateChange?.formatAsChange().orEmpty(), @@ -74,6 +83,21 @@ class WalletSelectorViewModel @Inject constructor( .formatFiat(balanceModel.fiatSymbol) ) ) + } else { + state + } + } + } + }.launchIn(viewModelScope) + } + + private fun observeScores() { + accountInteractor.observeNomisScores() + .onEach { scores -> + walletItemsFlow.update { oldStates -> + oldStates.map { state -> + val score = scores.find { it.metaId == state.id } + score?.let { state.copy(score = score.score) } ?: state } } } From 4db388a41dba786d3b4687c9a6c396dc1f152b27 Mon Sep 17 00:00:00 2001 From: Deneath Date: Thu, 8 Aug 2024 15:49:17 +0700 Subject: [PATCH 04/84] wip complete adding scoring to the address book screen Signed-off-by: Deneath --- .../component/AddressInputWithScore.kt | 127 +++++++++ .../common/compose/component/InputWithHint.kt | 7 +- .../common/compose/component/ScoreStar.kt | 18 +- .../common/data/network/AndroidLogger.kt | 5 +- .../common/di/modules/NetworkModule.kt | 25 +- .../drawable/ic_score_star_full_24_pink.xml | 9 + common/src/main/res/values-in/strings.xml | 1 + common/src/main/res/values-ja/strings.xml | 1 + common/src/main/res/values-pt/strings.xml | 1 + common/src/main/res/values-ru/strings.xml | 3 +- common/src/main/res/values-tr/strings.xml | 1 + common/src/main/res/values-vi/strings.xml | 1 + common/src/main/res/values-zh/strings.xml | 1 + common/src/main/res/values/strings.xml | 10 +- .../co/soramitsu/coredb/dao/NomisScoresDao.kt | 2 +- .../co/soramitsu/coredb/dao/OperationDao.kt | 16 ++ .../domain/interfaces/AccountInteractor.kt | 6 - .../domain/interfaces/AccountRepository.kt | 2 +- .../domain/interfaces/NomisScoreInteractor.kt | 17 ++ .../api/domain/model/NomisScoreData.kt | 7 +- .../account/impl/data/mappers/Mappers.kt | 26 +- .../data/repository/AccountRepositoryImpl.kt | 4 +- .../account/impl/di/AccountFeatureModule.kt | 18 +- .../impl/domain/AccountInteractorImpl.kt | 28 +- .../impl/domain/NomisScoreInteractorImpl.kt | 82 ++++++ .../nomis_scoring/ScoreDetailsScreen.kt | 5 +- .../nomis_scoring/ScoreDetailsViewModel.kt | 10 +- .../presentation/profile/ProfileFragment.kt | 5 + .../presentation/profile/ProfileViewModel.kt | 22 +- .../src/main/res/layout/fragment_profile.xml | 36 +++ .../domain/interfaces/WalletInteractor.kt | 1 + .../impl/data/repository/HistoryRepository.kt | 9 +- .../impl/domain/WalletInteractorImpl.kt | 26 +- .../balance/list/BalanceListViewModel.kt | 6 +- .../walletselector/SelectWalletViewModel.kt | 4 +- .../light/WalletSelectorViewModel.kt | 6 +- .../history/AddressHistoryContent.kt | 32 ++- .../history/AddressHistoryViewModel.kt | 250 +++++++++++++++--- .../send/setup/SendSetupContent.kt | 14 +- .../send/setup/SendSetupViewModel.kt | 80 ++++-- 40 files changed, 759 insertions(+), 165 deletions(-) create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/AddressInputWithScore.kt create mode 100644 common/src/main/res/drawable/ic_score_star_full_24_pink.xml create mode 100644 feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/NomisScoreInteractor.kt create mode 100644 feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/NomisScoreInteractorImpl.kt diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/AddressInputWithScore.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/AddressInputWithScore.kt new file mode 100644 index 0000000000..0e605c227a --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/AddressInputWithScore.kt @@ -0,0 +1,127 @@ +package jp.co.soramitsu.common.compose.component + +import androidx.compose.foundation.clickable +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.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.black1 +import jp.co.soramitsu.common.compose.theme.black2 +import jp.co.soramitsu.common.compose.theme.white24 +import jp.co.soramitsu.common.compose.theme.white50 +import jp.co.soramitsu.common.utils.withNoFontPadding + +sealed interface AddressInputWithScore { + val title: String + + data class Filled( + override val title: String, + val address: String, + val image: Any, + val score: Int? + ) : AddressInputWithScore + + data class Empty( + override val title: String, + val hint: String + ) : AddressInputWithScore +} + +@Composable +fun AddressInputWithScore( + state: AddressInputWithScore, + onPaste: () -> Unit, + onClear: () -> Unit +) { + + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + borderColor = white24 + ) { + Row(modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + AsyncImage( + model = (state as? AddressInputWithScore.Filled)?.image + ?: R.drawable.ic_address_placeholder, + contentDescription = null, + modifier = Modifier.size(32.dp) + ) + MarginHorizontal(margin = 8.dp) + Column(modifier = Modifier.weight(1f)) { + H5( + text = state.title.withNoFontPadding(), + color = black2 + ) + when (state) { + is AddressInputWithScore.Empty -> { + B1(text = state.hint, color = black1) + } + + is AddressInputWithScore.Filled -> { + Row(verticalAlignment = Alignment.CenterVertically) { + B1( + text = state.address, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.weight(1f, fill = false), + color = white50 + ) + MarginHorizontal(margin = 8.dp) + state.score?.let { ScoreStar(score = it) } + } + + } + } + } + MarginHorizontal(margin = 8.dp) + when (state) { + is AddressInputWithScore.Empty -> { + Badge( + iconResId = R.drawable.ic_copy_16, + labelResId = R.string.chip_paste, + onClick = onPaste + ) + } + + is AddressInputWithScore.Filled -> { + Image( + res = R.drawable.ic_close_16_circle, + modifier = Modifier + .wrapContentSize() + .clickable(onClick = onClear) + ) + } + } + } + } +} + +@Preview +@Composable +private fun AccountInputPreview() { + val filled = AddressInputWithScore.Filled( + "Send to", + "BlueBird", + "0x23d2ef23...23d23f23", + 100 + ) + val empty = AddressInputWithScore.Empty("Send to", "Public address") + Column { + AddressInputWithScore(filled, onClear = {}, onPaste = {}) + MarginVertical(margin = 6.dp) + AddressInputWithScore(empty, onClear = {}, onPaste = {}) + MarginVertical(margin = 6.dp) + } +} diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/InputWithHint.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/InputWithHint.kt index 3211d07d41..433ea34640 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/InputWithHint.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/InputWithHint.kt @@ -3,7 +3,6 @@ package jp.co.soramitsu.common.compose.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.MaterialTheme @@ -22,11 +21,10 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import jp.co.soramitsu.common.utils.formatting.shortenAddress import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.utils.formatting.shortenAddress import jp.co.soramitsu.common.utils.withNoFontPadding @Composable @@ -34,7 +32,7 @@ fun InputWithHint( state: String?, modifier: Modifier = Modifier, cursorBrush: Brush = SolidColor(white), - inputFieldModifier: Modifier = Modifier, + inputFieldModifier: Modifier = Modifier,//.fillMaxWidth(), editable: Boolean = true, onInput: (String) -> Unit, Hint: @Composable () -> Unit @@ -60,7 +58,6 @@ fun InputWithHint( enabled = editable, keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Text, imeAction = ImeAction.None), modifier = inputFieldModifier - .fillMaxWidth() .align(Alignment.CenterStart) .onFocusChanged { focusedState = it.isFocused diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt index f82301af45..11f915c059 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt @@ -16,7 +16,7 @@ import jp.co.soramitsu.common.compose.theme.warningYellow import jp.co.soramitsu.common.compose.theme.white30 @Composable -fun ScoreStar(score: Int) { +fun ScoreStar(score: Int, modifier: Modifier = Modifier) { val (starRes, color) = when (score) { in 0..33 -> R.drawable.ic_score_star_empty to warningOrange in 33..66 -> R.drawable.ic_score_star_half to warningYellow @@ -26,21 +26,25 @@ fun ScoreStar(score: Int) { when { score >= 0 -> { - Row { + Row(modifier = modifier) { Image(res = starRes, tint = color, modifier = Modifier.size(12.dp)) MarginHorizontal(margin = 3.dp) B2(text = score.toString(), color = color) } } + score == -1 -> { // loading - Image(res = starRes, tint = color, modifier = Modifier - .size(12.dp) - .shimmer()) + Image( + res = starRes, tint = color, modifier = modifier + .size(12.dp) + .shimmer() + ) } + score == -2 -> { //error - Row { + Row(modifier = modifier) { Image(res = starRes, tint = color, modifier = Modifier.size(12.dp)) MarginHorizontal(margin = 3.dp) B2(text = "N/A", color = color) @@ -51,7 +55,7 @@ fun ScoreStar(score: Int) { @Preview @Composable -private fun ScoreStarPreview(){ +private fun ScoreStarPreview() { FearlessAppTheme { Column { ScoreStar(score = -1) diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/AndroidLogger.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/AndroidLogger.kt index 314f778987..9f77f6b14e 100644 --- a/common/src/main/java/jp/co/soramitsu/common/data/network/AndroidLogger.kt +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/AndroidLogger.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.common.data.network -import android.util.Log import jp.co.soramitsu.common.BuildConfig import jp.co.soramitsu.shared_utils.wsrpc.logging.Logger @@ -9,13 +8,13 @@ const val TAG = "AndroidLogger" class AndroidLogger : Logger { override fun log(message: String?) { if (BuildConfig.DEBUG) { - Log.d(TAG, message.toString()) +// Log.d(TAG, message.toString()) } } override fun log(throwable: Throwable?) { if (BuildConfig.DEBUG) { - throwable?.printStackTrace() +// throwable?.printStackTrace() } } } diff --git a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt index b17c4d83ef..2e717a1bd1 100644 --- a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt +++ b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt @@ -24,16 +24,17 @@ import jp.co.soramitsu.shared_utils.wsrpc.recovery.Reconnector import jp.co.soramitsu.shared_utils.wsrpc.request.CoroutinesRequestExecutor import jp.co.soramitsu.shared_utils.wsrpc.request.RequestExecutor import okhttp3.Cache +import okhttp3.CacheControl import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory private const val HTTP_CACHE = "http_cache" +private const val NOMIS_CACHE = "nomis_cache" private const val CACHE_SIZE = 50L * 1024L * 1024L // 50 MiB private const val TIMEOUT_SECONDS = 60L -private const val NOMIS_TIMEOUT_MINUTES = 5L +private const val NOMIS_TIMEOUT_MINUTES = 2L @InstallIn(SingletonComponent::class) @Module @@ -65,7 +66,7 @@ class NetworkModule { .retryOnConnectionFailure(true) if (BuildConfig.DEBUG) { - builder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) +// builder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) } return builder.build() @@ -73,12 +74,13 @@ class NetworkModule { @Provides @Singleton - fun provideNomisHttpClient(): NomisApi { + fun provideNomisHttpClient(context: Context): NomisApi { val builder = OkHttpClient.Builder() -// .connectTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) -// .writeTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) -// .readTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .connectTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .writeTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .readTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) .callTimeout(NOMIS_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .cache(Cache(File(context.cacheDir, NOMIS_CACHE), CACHE_SIZE)) .addInterceptor { val request = it.request().newBuilder().apply { addHeader("X-API-Key", "j9Us1Kxoo9fs3nD") @@ -86,6 +88,15 @@ class NetworkModule { }.build() it.proceed(request) } + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .cacheControl( + CacheControl.Builder() + .maxAge(24, TimeUnit.HOURS) + .build()) + .build() + chain.proceed(request) + } .retryOnConnectionFailure(true) val gson = Gson() diff --git a/common/src/main/res/drawable/ic_score_star_full_24_pink.xml b/common/src/main/res/drawable/ic_score_star_full_24_pink.xml new file mode 100644 index 0000000000..1a2f9e35b3 --- /dev/null +++ b/common/src/main/res/drawable/ic_score_star_full_24_pink.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index 539ec9473a..c39723e1b0 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -727,6 +727,7 @@ Cari berdasarkan jaringan Alamat akun atau nama akun Tidak ada hasil pencarian + Alamat publik Alamatnya tidak cocok dengan jaringan tujuan. Anda harus memilih jaringan lain Memiliki obligasi minimum yang relevan PENAFIAN: Saran kolator algoritmik bukan merupakan konsultasi atau nasihat keuangan. Staking adalah aktivitas berisiko tinggi, dan saran kolator algoritmik tidak serta merta memitigasi risiko ini. Seorang pengumpul yang disarankan oleh algoritme masih dapat meninggalkan kumpulan kandidat. Pengumpul yang disarankan oleh algoritme juga dapat mengubah parameternya (misalnya, tarif komisi, dll.) kapan saja setelah disarankan dan/atau dipilih. Anda bisa kehilangan hadiah karena alasan ini atau lainnya. Hanya pertaruhkan token dan gunakan saran kolektor sesuai kebijaksanaan Anda sendiri, setelah melakukan uji tuntas dan dengan cermat mempertimbangkan risiko yang ada. diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 3f89386793..93df6052c3 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -732,6 +732,7 @@ ネットワークで検索 アカウントのアドレスまたはアカウント名 検索結果がありません + パブリック・アドレス アドレスが宛先ネットワークと一致しません。別のネットワークを選択してください 関連する最低保証金があること 免責事項:アルゴリズム・コレーターの提案は、金融に関する相談や助言に該当するものではありません。ステーキングはハイリスクな活動であり、アルゴリズムによるコレーターの提案は、必ずしもこのリスクを軽減するものではありません。アルゴリズムによって提案されたコレーターは、候補から外れる可能性があります。アルゴリズムによって提案されたコレーターは、提案および/または選択された後、いつでもパラメーター(例:手数料率など)を変更することができます。これらの理由や他の理由で報酬を失う可能性があります。関連するリスクを注意して慎重に検討した上で、ご自身の判断でトークンをステークし、コレーターの提案を使用するようにしてください diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index a805fe996a..8207fd3982 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -710,6 +710,7 @@ Pesquisar por rede Endereço da conta ou nome da conta Nenhum resultado da pesquisa + Endereço público O endereço não corresponde à rede de destino. Você deve selecionar outra rede Ter vínculo mínimo relevante AVISO: Os detentores de tokens MOVR/GLMR devem realizar a cuidadosa devida diligência acerca dos validadores antes de delegar. Ser listado como um validador não representa um endosso ou recomendação da Moonbeam Network, da Moonriver Network ou da Moonbeam Foundation. A Moonbeam Network, a Moonriver Network e a Moonbeam Foundation não examinaram os validadores da lista e não assumem nenhuma responsabilidade com relação à seleção, desempenho, segurança, precisão ou uso de quaisquer ofertas de terceiros. Você é o único responsável por fazer a sua própria diligência para entender as taxas aplicáveis e todos os riscos presentes, incluindo o monitoramento ativo da atividade dos seus validadores. diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 4423ecc02a..fdffef1b6c 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -420,7 +420,7 @@ ПРИМЕЧАНИЕ Убрать ликвидность Убрать ликвидность - Заработать %s + Заработать %@ Выплата вознаграждений в Проскальзывание Добавить ликвидность @@ -631,6 +631,7 @@ Поиск по сети Адрес аккаунта или имя аккаунта Нет результатов + Публичный адрес Релевантный минимальный стейк Импорт из Google JSON diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 33da037cff..716e6a0c92 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -748,6 +748,7 @@ Ağa göre ara Hesap adresi veya hesap adı Arama sonucu bulunamadı + Açık adresi Adres hedef ağla eşleşmiyor. Başka bir ağ seçmelisiniz İlgili minimum teminata sahip olmak UYARI: MOVR/GLMR token sahipleri, delege etmeden önce harmanlayıcıları üzerinde dikkatli bir araştırma yapmalıdır. Bir harmanlayıcı olarak listelenmek, Moonbeam Network, Moonriver Network veya Moonbeam Foundation\'ın bir onayı veya tavsiyesi değildir. Ne Moonbeam Network, ne Moonriver Network, ne de Moonbeam Foundation, listedeki harmanlayıcıları incelememiştir ve herhangi bir üçüncü taraf tekliflerinin seçimi, performansı, güvenliği, doğruluğu veya kullanımı ile ilgili hiçbir sorumluluk kabul etmez. Harmanlayıcılarınızın faaliyetlerini aktif bir şekilde takip etmek de dahil olmak üzere, uygulanabilecek ücretleri ve mevcut riskleri anlamak için gereken tüm özeni göstermekten yalnızca siz sorumlusunuz diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index c95433f4be..d1c94b32a7 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -757,6 +757,7 @@ Tìm kiếm theo mạng Địa chỉ tài khoản hoặc tên tài khoản Không có kết quả tìm kiếm + Địa chỉ public Địa chỉ không khớp với mạng đích. Bạn nên chọn mạng khác Có trái phiếu tối thiểu có liên quan TUYÊN BỐ TỪ CHỐI: Các đề xuất của trình xác minh thuật toán không cấu thành tư vấn hoặc lời khuyên tài chính. Staking là một hoạt động có rủi ro cao và các đề xuất của trình xác thực thuật toán không nhất thiết giảm thiểu rủi ro này. Một сollator do thuật toán đề xuất vẫn có thể rời khỏi nhóm ứng cử viên. Một сollator do thuật toán đề xuất cũng có thể thay đổi các thông số của chúng (ví dụ: tỷ lệ hoa hồng, v.v.) bất kỳ lúc nào sau khi được đề xuất và/hoặc chọn. Bạn có thể mất phần thưởng vì những lý do này hoặc lý do khác. Chỉ đặt cược mã thông báo và sử dụng các đề xuất сollator theo quyết định của riêng bạn, sau khi tiến hành thẩm định và xem xét cẩn thận các rủi ro liên quan. diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml index 44f75500de..3063c13eb0 100644 --- a/common/src/main/res/values-zh/strings.xml +++ b/common/src/main/res/values-zh/strings.xml @@ -754,6 +754,7 @@ 按网络搜索 账户地址或账户名称 无搜索结果 + 公共地址 地址与目标网络不匹配。您应该选择另一个网络。 具备相关的最低绑定额 免责声明:算法收集人建议并不构成金融咨询或建议。质押是一项高风险活动,算法收集人建议并不能完全减轻这种风险。由算法提供的收集人仍有可能离开候选池。由算法提供的收集人也可以在被推荐和/或选择后随时更改其参数(例如佣金率等)。您可能因此或其他原因而失去奖励。请在进行尽职调查和慎重考虑风险后,根据自己的判断谨慎使用质押代币和使用收集人建议。 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index a36a382be4..6cc1ac10e9 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -726,6 +726,7 @@ Your total stake The limit of pools in this network has been reached You cannot create more pools + Nomis multichain score Accounts Dapps and more... Experimental features @@ -771,7 +772,11 @@ Additional: We strongly recommend that you don\'t send %s to this account. This address has been flagged due to evidence of a scam. - This address has been flagged due to evidence of a scam. We strongly recommend that you don\'t send %s to this account. + The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker + Nomis multi-chain score + Low network activity + Proceed with caution + This address has been flagged due to evidence of a scam. We strongly recommend that you don\'t send {asset} to this account. Scan code from receiver QR Code Upload from gallery @@ -780,6 +785,7 @@ Search by network Account address or account name No search results + Public address The address doesn\'t match the destination network. You should select another network Having relevant minimum bond DISCLAIMER: Algorithmic сollator suggestions do not constitute financial consultation or advice. Staking is a high-risk activity, and algorithmic сollator suggestions do not necessarily mitigate this risk. A сollator suggested by the algorithm could still leave the pool of candidates. A сollator suggested by the algorithm could also change their parameters (e.g.,commission rates, etc.) at any time after having been suggested and/or selected. You could lose rewards for these or other reasons. Only stake tokens and use сollator suggestions at your own discretion, after conducting due diligence and carefully considering the risks involved. @@ -1188,4 +1194,4 @@ What accounts in the wallet do you want to export? Yesterday Your collator - \ No newline at end of file + diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt index d1ea0e91fe..fec70ae17c 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/NomisScoresDao.kt @@ -22,5 +22,5 @@ interface NomisScoresDao { fun observeScores(): Flow> @Query("SELECT * FROM nomis_wallet_score WHERE metaId = :metaId") - fun observeScore(metaId: Long): Flow + fun observeScore(metaId: Long): Flow } \ No newline at end of file diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/OperationDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/OperationDao.kt index dd4d7b644a..f2ce6df6d8 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/OperationDao.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/OperationDao.kt @@ -59,6 +59,22 @@ abstract class OperationDao { ) abstract fun observeOperationAddresses(chainId: String, address: String, limit: Int): Flow> + @Query(""" + SELECT DISTINCT(CASE + WHEN address != sender THEN sender + WHEN address != receiver THEN receiver + ELSE NULL + END) AS result + FROM operations + WHERE chainId = :chainId + AND address = :address + AND result IS NOT NULL + ORDER BY time DESC + LIMIT :limit + """ + ) + abstract fun getOperationAddresses(chainId: String, address: String, limit: Int): List + @Transaction open suspend fun insertFromSubquery( accountAddress: String, diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt index bd91ec7c21..cd580f06d6 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountInteractor.kt @@ -5,7 +5,6 @@ import jp.co.soramitsu.account.api.domain.model.Account import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount -import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.backup.domain.models.BackupAccountType import jp.co.soramitsu.common.data.secrets.v2.ChainAccountSecrets import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets @@ -163,9 +162,4 @@ interface AccountInteractor { suspend fun updateFavoriteChain(chainId: ChainId, isFavorite: Boolean, metaId: Long) suspend fun selectedLightMetaAccount(): LightMetaAccount fun observeSelectedMetaAccountFavoriteChains(): Flow> - - fun observeNomisScores(): Flow> - - fun observeCurrentAccountScore(): Flow - fun observeAccountScore(metaId: Long): Flow } diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt index 6d89781740..bd8d955507 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AccountRepository.kt @@ -191,5 +191,5 @@ interface AccountRepository { fun observeFavoriteChains(metaId: Long): Flow> fun observeNomisScores(): Flow> - fun observeNomisScore(metaId: Long): Flow + fun observeNomisScore(metaId: Long): Flow } diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/NomisScoreInteractor.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/NomisScoreInteractor.kt new file mode 100644 index 0000000000..cefcacb798 --- /dev/null +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/NomisScoreInteractor.kt @@ -0,0 +1,17 @@ +package jp.co.soramitsu.account.api.domain.interfaces + +import jp.co.soramitsu.account.api.domain.model.NomisScoreData +import kotlinx.coroutines.flow.Flow + +interface NomisScoreInteractor { + fun observeNomisScores(): Flow> + + fun observeCurrentAccountScore(): Flow + fun observeAccountScore(metaId: Long): Flow + + var nomisMultichainScoreEnabled: Boolean + fun observeNomisMultichainScoreEnabled(): Flow + + suspend fun getNomisScore(address: String): NomisScoreData? + fun getNomisScoreFromMemoryCache(address: String): NomisScoreData? +} \ No newline at end of file diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt index 72da0e3693..5d4013b417 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/NomisScoreData.kt @@ -15,7 +15,12 @@ data class NomisScoreData( val maxTransactionTimeInHours: Double, val minTransactionTimeInHours: Double, val scoredAt: Long? -){ +) { val isError = score == -2 val isLoading = score == -1 + + companion object { + const val LOADING_CODE = -1 + const val ERROR_CODE = -2 + } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt index fdf42adad1..b534851a68 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/mappers/Mappers.kt @@ -34,11 +34,13 @@ fun mapCryptoTypeToCryptoTypeModel( R.string.sr25519_selection_subtitle ) }" + CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ resourceManager.getString( R.string.ed25519_selection_subtitle ) }" + CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ resourceManager.getString( R.string.ecdsa_selection_subtitle @@ -99,7 +101,7 @@ fun mapMetaAccountLocalToMetaAccount( keySelector = FavoriteChainLocal::chainId, valueTransform = { MetaAccount.FavoriteChain( - chain = chainsById[it.chainId], + chain = chainsById[it.chainId], isFavorite = it.isFavorite ) } @@ -175,7 +177,6 @@ fun NomisResponse.toLocal(metaId: Long): NomisWalletScoreLocal { fun NomisWalletScoreLocal.toDomain(): NomisScoreData { val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.getDefault()) -// sdf.timeZone = TimeZone.getTimeZone("UTC") val scoredAtMillis = runCatching { formatter.parse(scoredAt)?.time }.getOrNull() return NomisScoreData( @@ -192,4 +193,25 @@ fun NomisWalletScoreLocal.toDomain(): NomisScoreData { minTransactionTimeInHours = minTransactionTimeInHours, scoredAt = scoredAtMillis ) +} + +fun NomisResponse.toDomain(): NomisScoreData { + val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.getDefault()) + val scoredAtMillis = runCatching { formatter.parse(data.stats.scoredAt)?.time }.getOrNull() + + val score = (data.score * 100).toInt() + return NomisScoreData( + metaId = -1, + score = score, + updated = System.currentTimeMillis(), + nativeBalanceUsd = data.stats.nativeBalanceUSD.toBigDecimal(), + holdTokensUsd = data.stats.holdTokensBalanceUSD.toBigDecimal(), + walletAgeInMonths = data.stats.walletAgeInMonths, + totalTransactions = data.stats.totalTransactions, + rejectedTransactions = data.stats.totalRejectedTransactions, + avgTransactionTimeInHours = data.stats.averageTransactionTimeInHours, + maxTransactionTimeInHours = data.stats.maxTransactionTimeInHours, + minTransactionTimeInHours = data.stats.minTransactionTimeInHours, + scoredAt = scoredAtMillis + ) } \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt index fa795c4680..948e81a30b 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/AccountRepositoryImpl.kt @@ -934,9 +934,9 @@ class AccountRepositoryImpl( } } - override fun observeNomisScore(metaId: Long): Flow { + override fun observeNomisScore(metaId: Long): Flow { return nomisScoresDao.observeScore(metaId).map { score -> - score.toDomain() + score?.toDomain() } } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt index 767eecf2cf..32662d9a63 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt @@ -9,6 +9,7 @@ import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.interfaces.AssetNotNeedAccountUseCase +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.SelectedAccountUseCase import jp.co.soramitsu.account.api.domain.updaters.AccountUpdateScope import jp.co.soramitsu.account.api.presentation.account.AddressDisplayUseCase @@ -23,12 +24,14 @@ import jp.co.soramitsu.account.impl.domain.AccountInteractorImpl import jp.co.soramitsu.account.impl.domain.AssetNotNeedAccountUseCaseImpl import jp.co.soramitsu.account.impl.domain.BeaconConnectedUseCase import jp.co.soramitsu.account.impl.domain.NodeHostValidator +import jp.co.soramitsu.account.impl.domain.NomisScoreInteractorImpl import jp.co.soramitsu.account.impl.domain.account.details.AccountDetailsInteractor import jp.co.soramitsu.account.impl.presentation.common.mixin.api.CryptoTypeChooserMixin import jp.co.soramitsu.account.impl.presentation.common.mixin.impl.CryptoTypeChooser import jp.co.soramitsu.common.data.network.AppLinksProvider import jp.co.soramitsu.common.data.network.NetworkApiCreator import jp.co.soramitsu.common.data.network.coingecko.CoingeckoApi +import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.secrets.v1.SecretStoreV1 import jp.co.soramitsu.common.data.secrets.v2.SecretStoreV2 import jp.co.soramitsu.common.data.storage.Preferences @@ -104,9 +107,20 @@ class AccountFeatureModule { @Provides fun provideAccountInteractor( accountRepository: AccountRepository, - fileProvider: FileProvider + fileProvider: FileProvider, + preferences: Preferences ): AccountInteractor { - return AccountInteractorImpl(accountRepository, fileProvider) + return AccountInteractorImpl(accountRepository, fileProvider, preferences) + } + + @Provides + @Singleton + fun provideNomisScoresInteractor( + accountRepository: AccountRepository, + preferences: Preferences, + nomisApi: NomisApi + ): NomisScoreInteractor { + return NomisScoreInteractorImpl(accountRepository, preferences, nomisApi) } @Provides diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt index aaf4590ab3..1f303ae7d7 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AccountInteractorImpl.kt @@ -7,15 +7,13 @@ import jp.co.soramitsu.account.api.domain.model.Account import jp.co.soramitsu.account.api.domain.model.ImportJsonData import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccountOrdering -import jp.co.soramitsu.account.api.domain.model.NomisScoreData +import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.common.interfaces.FileProvider -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.core.model.Language import jp.co.soramitsu.core.models.CryptoType import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn @@ -24,6 +22,7 @@ import kotlinx.coroutines.withContext class AccountInteractorImpl( private val accountRepository: AccountRepository, private val fileProvider: FileProvider, + private val preferences: Preferences, private val context: CoroutineContext = Dispatchers.Default ) : AccountInteractor { @@ -229,7 +228,8 @@ class AccountInteractorImpl( override fun selectedMetaAccountFlow() = accountRepository.selectedMetaAccountFlow() - override suspend fun selectedMetaAccount() = withContext(context) { accountRepository.getSelectedMetaAccount() } + override suspend fun selectedMetaAccount() = + withContext(context) { accountRepository.getSelectedMetaAccount() } override suspend fun selectedLightMetaAccount() = accountRepository.getSelectedLightMetaAccount() @@ -330,24 +330,4 @@ class AccountInteractorImpl( accountRepository.observeFavoriteChains(it.id) }.flowOn(Dispatchers.IO) } - - override fun observeNomisScores(): Flow> { - return accountRepository.observeNomisScores() - .flowOn(context) - } - - override fun observeCurrentAccountScore(): Flow { - return selectedMetaAccountFlow().flatMapLatest { - accountRepository.observeNomisScore(it.id) - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - override fun observeAccountScore(metaId: Long): Flow { - return flowOf { - accountRepository.getMetaAccount(metaId) - }.flatMapLatest { - accountRepository.observeNomisScore(it.id) - } - } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/NomisScoreInteractorImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/NomisScoreInteractorImpl.kt new file mode 100644 index 0000000000..22663da1e6 --- /dev/null +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/NomisScoreInteractorImpl.kt @@ -0,0 +1,82 @@ +package jp.co.soramitsu.account.impl.domain + +import android.util.Log +import java.util.concurrent.ConcurrentHashMap +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor +import jp.co.soramitsu.account.api.domain.model.NomisScoreData +import jp.co.soramitsu.account.impl.data.mappers.toDomain +import jp.co.soramitsu.common.data.network.nomis.NomisApi +import jp.co.soramitsu.common.data.storage.Preferences +import jp.co.soramitsu.common.utils.flowOf +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.withContext + +class NomisScoreInteractorImpl( + private val accountRepository: AccountRepository, + private val preferences: Preferences, + private val nomisApi: NomisApi, + private val coroutineContext: CoroutineContext = Dispatchers.Default +) + : NomisScoreInteractor { + + private val scoresCache = ConcurrentHashMap() + + @OptIn(ExperimentalCoroutinesApi::class) + override fun observeNomisScores(): Flow> { + return observeNomisMultichainScoreEnabled().flatMapLatest { + if(it) { + accountRepository.observeNomisScores() + } else { + kotlinx.coroutines.flow.flowOf(emptyList()) + } + }.flowOn(coroutineContext) + } + + override fun observeCurrentAccountScore(): Flow { + return observeNomisMultichainScoreEnabled().flatMapLatest { + if(it) { + accountRepository.selectedMetaAccountFlow() + } else { + kotlinx.coroutines.flow.flowOf(null) + } + } + .flatMapLatest { metaAccount -> + metaAccount?.let { accountRepository.observeNomisScore(it.id) } ?: kotlinx.coroutines.flow.flowOf(null) + }.flowOn(coroutineContext) + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun observeAccountScore(metaId: Long): Flow { + return flowOf { + accountRepository.getMetaAccount(metaId) + }.flatMapLatest { + accountRepository.observeNomisScore(it.id) + } + } + + override fun observeNomisMultichainScoreEnabled(): Flow { + return preferences.booleanFlow("nomis_multichain_score_enabled", true) + } + + override var nomisMultichainScoreEnabled: Boolean + get() = preferences.getBoolean("nomis_multichain_score_enabled", true) + set(value) { + preferences.putBoolean("nomis_multichain_score_enabled", value) + } + + override suspend fun getNomisScore(address: String): NomisScoreData? { + return scoresCache.getOrPut(address) { + withContext(coroutineContext) {runCatching { nomisApi.getNomisScore(address) }.onFailure { Log.d("&&&", "failed to load nomis score: $it") }.getOrNull()?.toDomain() }?: return null + } + } + + override fun getNomisScoreFromMemoryCache(address: String): NomisScoreData? { + return scoresCache[address] + } +} \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt index d07f49ac76..c0f2eca421 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.valentinilk.shimmer.shimmer +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.common.R import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.Address @@ -246,7 +247,7 @@ fun ScoreBar(score: Int, modifier: Modifier = Modifier) { } } - score == -1 -> { + score == NomisScoreData.LOADING_CODE -> { repeat(5) { Image( res = R.drawable.ic_score_star_empty, @@ -258,7 +259,7 @@ fun ScoreBar(score: Int, modifier: Modifier = Modifier) { } } - score == -2 -> { + score == NomisScoreData.ERROR_CODE -> { repeat(5) { Image( res = R.drawable.ic_score_star_empty, diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt index 5648f1f63e..21bccc735c 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt @@ -8,6 +8,8 @@ import java.util.Date import java.util.Locale import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.account.impl.presentation.AccountRouter import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.resources.ClipboardManager @@ -26,6 +28,7 @@ import kotlinx.coroutines.launch class ScoreDetailsViewModel @Inject constructor( private val router: AccountRouter, private val accountInteractor: AccountInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, private val clipboardManager: ClipboardManager, private val resourceManager: ResourceManager, private val savedStateHandle: SavedStateHandle @@ -42,11 +45,12 @@ class ScoreDetailsViewModel @Inject constructor( init { val metaAccountId = requireNotNull(savedStateHandle.get(ScoreDetailsFragment.META_ACCOUNT_ID_KEY)) - accountInteractor.observeAccountScore(metaAccountId) + nomisScoreInteractor.observeAccountScore(metaAccountId) .onEach { nomisData -> + nomisData ?: return@onEach val newInfoState = when (nomisData.score) { - -1 -> ScoreDetailsViewState.Loading - -2 -> ScoreDetailsViewState.Error + NomisScoreData.LOADING_CODE -> ScoreDetailsViewState.Loading + NomisScoreData.ERROR_CODE -> ScoreDetailsViewState.Error else -> { val scoredAt = nomisData.scoredAt?.let { val formatter = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt index 875259f5d4..52e01b2a24 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt @@ -51,6 +51,7 @@ class ProfileFragment : BaseFragment() { polkaswapDisclaimerTv.setOnClickListener { viewModel.polkaswapDisclaimerClicked() } profileSoraCard.setOnClickListener { viewModel.onSoraCardClicked() } profileWalletConnect.setOnClickListener { viewModel.onWalletConnectClick() } + nomisMultichainScoreContainer.setOnClickListener { viewModel.onNomisMultichainScoreContainerClick() } viewModel.hasChainsWithNoAccountFlow.observe { missingAccountsIcon.isVisible = it @@ -82,6 +83,10 @@ class ProfileFragment : BaseFragment() { viewModel.showFiatChooser.observeEvent(::showFiatChooser) viewModel.selectedFiatLiveData.observe(binding.selectedCurrencyTv::setText) + + viewModel.nomisMultichainScoreEnabledFlow.observe { + binding.nomisMultichainScoreSwitch.isChecked = it + } } private fun showFiatChooser(payload: DynamicListBottomSheet.Payload) { diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt index 541a07fba9..aabc035bbf 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.presentation.actions.ExternalAccountActions @@ -27,12 +28,15 @@ import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.view.bottomSheet.list.dynamic.DynamicListBottomSheet import jp.co.soramitsu.soracard.api.domain.SoraCardInteractor import jp.co.soramitsu.soracard.impl.presentation.SoraCardItemViewState -import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -42,6 +46,7 @@ private const val AVATAR_SIZE_DP = 32 class ProfileViewModel @Inject constructor( private val interactor: AccountInteractor, private val accountDetailsInteractor: AccountDetailsInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, private val soraCardInteractor: SoraCardInteractor, private val router: AccountRouter, private val addressIconGenerator: AddressIconGenerator, @@ -77,12 +82,23 @@ class ProfileViewModel @Inject constructor( val hasChainsWithNoAccountFlow = accountDetailsInteractor.hasChainsWithNoAccount() .stateIn(this, SharingStarted.Eagerly, false) + private val _nomisMultichainScoreEnabledFlow = MutableStateFlow(nomisScoreInteractor.nomisMultichainScoreEnabled) + val nomisMultichainScoreEnabledFlow: Flow = _nomisMultichainScoreEnabledFlow + // private val soraCardState = soraCardInteractor.subscribeSoraCardInfo().map { // val kycStatus = it?.kycStatus?.let(::mapKycStatus) // SoraCardItemViewState(kycStatus, it, null, true) // } private val soraCardState = flowOf(SoraCardItemViewState()) + init { + nomisScoreInteractor.observeNomisMultichainScoreEnabled() + .onEach { + _nomisMultichainScoreEnabledFlow.value = it + } + .launchIn(viewModelScope) + } + fun aboutClicked() { router.openAboutScreen() } @@ -153,4 +169,8 @@ class ProfileViewModel @Inject constructor( fun onWalletConnectClick() { router.openConnectionsScreen() } + + fun onNomisMultichainScoreContainerClick() { + nomisScoreInteractor.nomisMultichainScoreEnabled = !nomisScoreInteractor.nomisMultichainScoreEnabled + } } diff --git a/feature-account-impl/src/main/res/layout/fragment_profile.xml b/feature-account-impl/src/main/res/layout/fragment_profile.xml index bf0fc294eb..d11ad07980 100644 --- a/feature-account-impl/src/main/res/layout/fragment_profile.xml +++ b/feature-account-impl/src/main/res/layout/fragment_profile.xml @@ -256,6 +256,42 @@ + + + + + + + + + + fun observeCurrentAccountChainsPerAsset(assetId: String): Flow> + suspend fun getOperationAddressWithChainId(chainId: ChainId, limit: Int?): Set } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt index 3a5f7c87b4..8051ed131b 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.wallet.impl.data.repository -import android.util.Log import jp.co.soramitsu.common.data.model.CursorPage import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.core.models.Asset @@ -19,6 +18,7 @@ import jp.co.soramitsu.wallet.impl.data.storage.TransferCursorStorage import jp.co.soramitsu.wallet.impl.domain.CurrentAccountAddressUseCase import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter import jp.co.soramitsu.wallet.impl.domain.model.Operation +import kotlin.coroutines.coroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -141,4 +141,11 @@ class HistoryRepository( addresses.toSet() } } + + suspend fun getOperationAddressWithChainId(chainId: ChainId, limit: Int?): Set = + withContext(coroutineContext) { + val address = currentAccountAddress.invoke(chainId) ?: return@withContext emptySet() + val operations = operationDao.getOperationAddresses(chainId, address, limit ?: NO_LIMIT) + return@withContext operations.toSet() + } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt index e3ae018735..2d683d8ad0 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt @@ -6,24 +6,22 @@ import com.mastercard.mpqr.pushpayment.model.PushPaymentData import com.mastercard.mpqr.pushpayment.parser.Parser import java.math.BigDecimal import java.math.BigInteger -import java.math.RoundingMode import java.net.URLDecoder import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.account.api.domain.model.address -import jp.co.soramitsu.common.compose.component.QuickAmountInput import jp.co.soramitsu.common.data.model.CursorPage import jp.co.soramitsu.common.data.network.runtime.binding.EqAccountInfo import jp.co.soramitsu.common.data.network.runtime.binding.EqOraclePricePoint import jp.co.soramitsu.common.data.storage.Preferences +import jp.co.soramitsu.common.domain.NetworkStateService import jp.co.soramitsu.common.domain.SelectedFiat +import jp.co.soramitsu.common.domain.model.NetworkIssueType import jp.co.soramitsu.common.interfaces.FileProvider import jp.co.soramitsu.common.mixin.api.UpdatesMixin import jp.co.soramitsu.common.mixin.api.UpdatesProviderUi -import jp.co.soramitsu.common.domain.NetworkStateService -import jp.co.soramitsu.common.domain.model.NetworkIssueType import jp.co.soramitsu.common.model.AssetBooleanState import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.mapList @@ -32,7 +30,6 @@ import jp.co.soramitsu.common.utils.requireValue import jp.co.soramitsu.core.models.Asset.StakingType import jp.co.soramitsu.core.models.ChainId import jp.co.soramitsu.core.utils.isValidAddress -import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.coredb.model.AssetUpdateItem import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository @@ -63,24 +60,18 @@ import jp.co.soramitsu.wallet.impl.domain.model.QrContentCBDC import jp.co.soramitsu.wallet.impl.domain.model.QrContentSora import jp.co.soramitsu.wallet.impl.domain.model.Transfer import jp.co.soramitsu.wallet.impl.domain.model.WalletAccount -import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.toPhishingModel -import jp.co.soramitsu.wallet.impl.presentation.send.setup.SendSetupViewModel import jp.co.soramitsu.xcm.domain.XcmEntitiesFetcher import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.withIndex import kotlinx.coroutines.withContext @@ -484,15 +475,22 @@ class WalletInteractorImpl( chainId: ChainId, limit: Int? ): Flow> = - historyRepository.getOperationAddressWithChainIdFlow(chainId, limit) + historyRepository.getOperationAddressWithChainIdFlow(chainId, limit).flowOn(coroutineContext) + + override suspend fun getOperationAddressWithChainId( + chainId: ChainId, + limit: Int? + ): Set = + withContext(coroutineContext){ historyRepository.getOperationAddressWithChainId(chainId, limit) } - override suspend fun saveAddress(name: String, address: String, selectedChainId: String) { + override suspend fun saveAddress(name: String, address: String, selectedChainId: String) = withContext(coroutineContext) { addressBookRepository.saveAddress(name, address, selectedChainId) } override fun observeAddressBook(chainId: ChainId) = addressBookRepository.observeAddressBook(chainId) .mapList { it.copy(address = it.address.trim()) } + .flowOn(coroutineContext) override fun saveChainId(walletId: Long, chainId: ChainId?) { preferences.putString(PREFS_WALLET_SELECTED_CHAIN_ID + walletId, chainId) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index cfc258fbf8..b6eb81c516 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import jp.co.soramitsu.account.api.domain.PendulumPreInstalledAccountsScenario import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.common.BuildConfig @@ -129,6 +130,7 @@ class BalanceListViewModel @Inject constructor( private val getAvailableFiatCurrencies: GetAvailableFiatCurrencies, private val selectedFiat: SelectedFiat, private val accountInteractor: AccountInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, private val updatesMixin: UpdatesMixin, private val resourceManager: ResourceManager, private val clipboardManager: ClipboardManager, @@ -447,10 +449,10 @@ class BalanceListViewModel @Inject constructor( } }.launchIn(viewModelScope) - accountInteractor.observeCurrentAccountScore() + nomisScoreInteractor.observeCurrentAccountScore() .onEach { score -> toolbarState.update { prevState -> - val newWalletIconState = (prevState.homeIconState as? ToolbarHomeIconState.Wallet)?.copy(score = score.score) + val newWalletIconState = (prevState.homeIconState as? ToolbarHomeIconState.Wallet)?.copy(score = score?.score) newWalletIconState?.let { prevState.copy(homeIconState = newWalletIconState) } ?: prevState diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletViewModel.kt index 8567243860..c9c4ca2738 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/SelectWalletViewModel.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import jp.co.soramitsu.account.api.domain.PendulumPreInstalledAccountsScenario import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.api.domain.model.ImportMode import jp.co.soramitsu.account.impl.presentation.account.mixin.api.AccountListingMixin @@ -42,6 +43,7 @@ private const val SUBSTRATE_BLOCKCHAIN_TYPE = 0 class SelectWalletViewModel @Inject constructor( accountListingMixin: AccountListingMixin, private val accountInteractor: AccountInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, private val router: WalletRouter, private val updatesMixin: UpdatesMixin, private val getTotalBalance: TotalBalanceUseCase, @@ -99,7 +101,7 @@ class SelectWalletViewModel @Inject constructor( } private fun observeScores() { - accountInteractor.observeNomisScores() + nomisScoreInteractor.observeNomisScores() .onEach { scores -> walletItemsFlow.update { oldStates -> oldStates.map { state -> diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt index f0f1d90c36..0099920c14 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/walletselector/light/WalletSelectorViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.impl.presentation.account.mixin.api.AccountListingMixin import jp.co.soramitsu.common.address.AddressIconGenerator @@ -33,7 +33,7 @@ class WalletSelectorViewModel @Inject constructor( accountListingMixin: AccountListingMixin, private val router: WalletRouter, private val totalBalanceUseCase: TotalBalanceUseCase, - private val accountInteractor: AccountInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, savedStateHandle: SavedStateHandle ) : BaseViewModel() { @@ -92,7 +92,7 @@ class WalletSelectorViewModel @Inject constructor( } private fun observeScores() { - accountInteractor.observeNomisScores() + nomisScoreInteractor.observeNomisScores() .onEach { scores -> walletItemsFlow.update { oldStates -> oldStates.map { state -> diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryContent.kt index ada9b93113..c94930fa37 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryContent.kt @@ -35,6 +35,7 @@ import jp.co.soramitsu.common.compose.component.FearlessProgress import jp.co.soramitsu.common.compose.component.H5 import jp.co.soramitsu.common.compose.component.MarginHorizontal import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.ScoreStar import jp.co.soramitsu.common.compose.component.ToolbarBottomSheet import jp.co.soramitsu.common.compose.theme.FearlessTheme import jp.co.soramitsu.common.compose.theme.black05 @@ -51,7 +52,8 @@ data class Address( val address: String, val image: Any, val chainId: ChainId, - val isSavedToContacts: Boolean + val isSavedToContacts: Boolean, + val score: Int? ) data class AddressHistoryViewState( @@ -208,18 +210,24 @@ fun AddressItem( val name = address.name.ifEmpty { stringResource(id = R.string.common_unknown) } - H5( - text = name.withNoFontPadding(), - color = black2, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + Row(modifier = Modifier.fillMaxWidth()) { + H5( + text = name.withNoFontPadding(), + color = black2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, false) + ) + MarginHorizontal(margin = 8.dp) + address.score?.let { ScoreStar(score = it) } + } MarginVertical(margin = 4.dp) B1( text = address.address.shortenAddress(), color = Color.White ) } + MarginHorizontal(margin = 8.dp) if (!address.isSavedToContacts) { BackgroundCorneredWithBorder( backgroundColor = black05, @@ -246,8 +254,14 @@ fun AddressItem( @Preview fun PreviewAddressHistoryContent() { val addressSet = setOf( - Address("Address 1 name of a very long text to show how it looks in UI", "address1qasd32dqa32e32r3qqed", R.drawable.ic_plus_circle, "", true), - Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false) + Address("Address 1 name of a very long text to show how it looks in UI", "address1qasd32dqa32e32r3qqed", R.drawable.ic_plus_circle, "", true, 99), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, 10), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, 60), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, 90), + Address("Address 1 name of a very long text to show how it looks in UI", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, 90), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, -1), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, -2), + Address("" ?: "John Mir", "32dfs4323AE3asdqa32e32r3qqed", R.drawable.ic_plus_circle, "", false, null) ) val addressBookAddresses = mapOf>("J" to addressSet.toList().subList(0, 1)) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryViewModel.kt index bca1356ad4..f6f4ad8e1e 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/history/AddressHistoryViewModel.kt @@ -1,28 +1,44 @@ package jp.co.soramitsu.wallet.impl.presentation.history +import android.graphics.drawable.PictureDrawable import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.address.createAddressIcon import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.presentation.dataOrNull import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import jp.co.soramitsu.wallet.impl.presentation.send.SendSharedState +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch @HiltViewModel class AddressHistoryViewModel @Inject constructor( val savedStateHandle: SavedStateHandle, val sharedState: SendSharedState, private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val nomisScoreInteractor: NomisScoreInteractor, private val resourceManager: ResourceManager, private val addressIconGenerator: AddressIconGenerator, private val router: WalletRouter @@ -32,52 +48,204 @@ class AddressHistoryViewModel @Inject constructor( private const val RECENT_SIZE = 10 } - val chainId: ChainId = savedStateHandle[AddressHistoryFragment.KEY_PAYLOAD] ?: error("ChainId not specified") + val chainId: ChainId = + savedStateHandle[AddressHistoryFragment.KEY_PAYLOAD] ?: error("ChainId not specified") + val chain = viewModelScope.async { chainsRepository.getChain(chainId) } - val state: StateFlow> = combine( - walletInteractor.getOperationAddressWithChainIdFlow(chainId, RECENT_SIZE), - walletInteractor.observeAddressBook(chainId) - ) { recentAddressesInfo, addressBook -> - val recentAddresses: Set
= recentAddressesInfo.map { address -> - val placeholder = resourceManager.getDrawable(R.drawable.ic_wallet) - val chain = walletInteractor.getChain(chainId) - val accountImage = address.ifEmpty { null }?.let { - addressIconGenerator.createAddressIcon(chain.isEthereumBased, address, AddressIconGenerator.SIZE_BIG) + val state: MutableStateFlow> = + MutableStateFlow(LoadingState.Loading()) + + private val addressBookFlow = + walletInteractor.observeAddressBook(chainId).map { LoadingState.Loaded(it) } + .stateIn(viewModelScope, SharingStarted.Eagerly, LoadingState.Loading()) + + private val recentAddressesDeferred = + async { walletInteractor.getOperationAddressWithChainId(chainId, RECENT_SIZE) } + + private val allAddressesFlow = addressBookFlow.map { + val data = it.dataOrNull()?.map { contact -> contact.address } ?: return@map emptySet() + val recentAddresses = recentAddressesDeferred.await() + (data + recentAddresses).toSet() + }.distinctUntilChanged() + + private val imagesFlow: MutableStateFlow> = MutableStateFlow(emptyMap()) + + init { + observeAddressBook() + observeImages() + observeScore() + } + + private fun observeAddressBook() { + val placeholder = resourceManager.getDrawable(R.drawable.ic_wallet) + addressBookFlow + .mapNotNull { it.dataOrNull() } + .onEach { addressBook -> + state.update { prevState -> + when (prevState) { + is LoadingState.Loading -> { + val newContacts = addressBook.map { contact -> + Address( + name = contact.name.orEmpty(), + address = contact.address.trim(), + image = imagesFlow.value[contact.address] ?: placeholder, + chainId = contact.chainId, + isSavedToContacts = true, + getCachedNomisScore(contact.address) + ) + }.groupBy { + it.name.firstOrNull()?.uppercase() + } + LoadingState.Loaded(AddressHistoryViewState(emptySet(), newContacts)) + } + + is LoadingState.Loaded -> { + val allPrevAddresses = + prevState.dataOrNull()?.addressBookAddresses?.values?.flatten() + ?: return@update prevState + val newContacts = addressBook.map { contact -> + val existingContact = + allPrevAddresses.find { it.address == contact.address } + + existingContact?.copy( + name = contact.name.orEmpty(), + address = contact.address.trim(), + image = imagesFlow.value[contact.address] ?: placeholder, + chainId = contact.chainId, + isSavedToContacts = true, + ) ?: Address( + name = contact.name.orEmpty(), + address = contact.address.trim(), + image = imagesFlow.value[contact.address] ?: placeholder, + chainId = contact.chainId, + isSavedToContacts = true, + prevState.data.recentAddresses.find { it.address == contact.address }?.score ?: getCachedNomisScore(contact.address) + ) + }.groupBy { + it.name.firstOrNull()?.uppercase() + } + + LoadingState.Loaded(prevState.data.copy(addressBookAddresses = newContacts)) + } + + else -> prevState + } + } } + .onEach { addressBook -> + val recentAddressesInfo = recentAddressesDeferred.await() + state.update { prevState -> + val data = prevState.dataOrNull() ?: return@update prevState + val newRecentAddressModels = recentAddressesInfo.map { address -> + val existing = data.recentAddresses.find { it.address == address } + existing?.copy(name = addressBook.firstOrNull { it.address == address }?.name.orEmpty(), + address = address.trim(), + chainId = chainId, + isSavedToContacts = address in addressBook.map { it.address }) + ?: Address( + name = addressBook.firstOrNull { it.address == address }?.name.orEmpty(), + address = address.trim(), + image = imagesFlow.value[address] ?: placeholder, + chainId = chainId, + isSavedToContacts = address in addressBook.map { it.address }, + score = getCachedNomisScore(address) + ) + }.toSet() + LoadingState.Loaded(data.copy(recentAddresses = newRecentAddressModels)) + } + } + .launchIn(viewModelScope) + } + + private suspend fun getCachedNomisScore(address: String): Int? { + return if(chain.await().let { it.isEthereumChain || it.isEthereumBased }) { + nomisScoreInteractor.getNomisScoreFromMemoryCache(address)?.score ?: NomisScoreData.LOADING_CODE + } else { + null + } + } - Address( - name = addressBook.firstOrNull { it.address == address }?.name.orEmpty(), - address = address.trim(), - image = accountImage ?: placeholder, - chainId = chainId, - isSavedToContacts = address in addressBook.map { it.address } - ) - }.toSet() + private fun observeImages() { + allAddressesFlow.onEach { allAddresses -> + val chain = chain.await() + coroutineScope { + allAddresses.forEach { address -> + viewModelScope.launch { + val accountImage = address.ifEmpty { null }?.let { + addressIconGenerator.createAddressIcon( + chain.isEthereumBased, + address, + AddressIconGenerator.SIZE_BIG + ) + } + imagesFlow.update { prevState -> + val newMap = prevState.toMutableMap() + newMap[address] = accountImage + newMap + } + } + } + } + }.launchIn(viewModelScope) - val addressBookAddresses = addressBook.map { contact -> + imagesFlow.onEach { images -> val placeholder = resourceManager.getDrawable(R.drawable.ic_wallet) - val chain = walletInteractor.getChain(contact.chainId) - val accountImage = contact.address.ifEmpty { null }?.let { - addressIconGenerator.createAddressIcon(chain.isEthereumBased, contact.address, AddressIconGenerator.SIZE_BIG) + state.update { prevState -> + val data = prevState.dataOrNull()?: return@update prevState + val addressBook = data.addressBookAddresses.mapValues { group -> + group.value.map { + it.copy(image = images[it.address] ?: placeholder) + } + } + val recentAddresses = data.recentAddresses.map { addressModel -> + addressModel.copy(image = images[addressModel.address] ?: placeholder) + }.toSet() + + LoadingState.Loaded(data.copy(recentAddresses = recentAddresses, addressBookAddresses = addressBook)) } - Address( - name = contact.name.orEmpty(), - address = contact.address.trim(), - image = accountImage ?: placeholder, - chainId = contact.chainId, - isSavedToContacts = true - ) - }.groupBy { - it.name.firstOrNull()?.uppercase() - } + }.launchIn(viewModelScope) + } - LoadingState.Loaded( - AddressHistoryViewState( - recentAddresses = recentAddresses, - addressBookAddresses = addressBookAddresses - ) - ) - }.stateIn(scope = this, started = SharingStarted.Eagerly, initialValue = LoadingState.Loading()) + private fun observeScore() { + allAddressesFlow.onEach { allAddresses -> + if(!chain.await().let { it.isEthereumChain || it.isEthereumBased }) return@onEach + coroutineScope { + allAddresses.forEach { address -> + val score = nomisScoreInteractor.getNomisScore(address)?.score ?: NomisScoreData.ERROR_CODE + state.update { prevState -> + if (prevState is LoadingState.Loaded) { + val addressBookWithScores = + prevState.data.addressBookAddresses.mapValues { group -> + group.value.map { + if (it.address == address) { + it.copy(score = score) + } else { + it + } + } + } + val recentAddressesWithScores = prevState.data.recentAddresses.map { + if (it.address == address) { + it.copy(score = score) + } else { + it + } + }.toSet() + + LoadingState.Loaded( + prevState.data.copy( + addressBookAddresses = addressBookWithScores, + recentAddresses = recentAddressesWithScores + ) + ) + } else { + prevState + } + } + } + } + }.launchIn(viewModelScope) + } override fun onAddressClick(address: Address) { sharedState.updateAddress(address.address) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupContent.kt index cfd4f42542..bc8819cc50 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupContent.kt @@ -38,8 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import java.math.BigDecimal import jp.co.soramitsu.common.compose.component.AccentDarkDisabledButton -import jp.co.soramitsu.common.compose.component.AddressInput -import jp.co.soramitsu.common.compose.component.AddressInputState +import jp.co.soramitsu.common.compose.component.AddressInputWithScore import jp.co.soramitsu.common.compose.component.AmountInput import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.B1 @@ -73,7 +72,7 @@ import jp.co.soramitsu.feature_wallet_impl.R data class SendSetupViewState( val toolbarState: ToolbarViewState, - val addressInputState: AddressInputState, + val addressInputState: AddressInputWithScore, val amountInputState: AmountInputViewState, val chainSelectorState: SelectorState, val feeInfoState: FeeInfoViewState, @@ -81,7 +80,7 @@ data class SendSetupViewState( val buttonState: ButtonViewState, val isSoftKeyboardOpen: Boolean, val isInputLocked: Boolean, - val quickAmountInputValues: List = QuickAmountInput.values().asList(), + val quickAmountInputValues: List = QuickAmountInput.entries, val isHistoryAvailable: Boolean, val sendAllChecked: Boolean, val sendAllAllowed: Boolean @@ -152,10 +151,9 @@ fun SendSetupContent( onNavigationClick = callback::onNavigationClick ) MarginVertical(margin = 16.dp) - AddressInput( + AddressInputWithScore( state = state.addressInputState, - onInput = callback::onAddressInput, - onInputClear = callback::onAddressInputClear, + onClear = callback::onAddressInputClear, onPaste = callback::onPasteClick ) @@ -297,7 +295,7 @@ private fun Badge( private fun SendSetupPreview() { val state = SendSetupViewState( toolbarState = ToolbarViewState("Send Fund", R.drawable.ic_arrow_left_24), - addressInputState = AddressInputState("Send to", "", ""), + addressInputState = AddressInputWithScore.Filled("Send to...", "0x23j2rf3bh8384j938", "", 100), amountInputState = AmountInputViewState( "KSM", "", diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt index f42796e7d0..5f06eb915b 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt @@ -9,12 +9,14 @@ import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor +import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.address.createAddressIcon import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.base.errors.ValidationException import jp.co.soramitsu.common.base.errors.ValidationWarning -import jp.co.soramitsu.common.compose.component.AddressInputState +import jp.co.soramitsu.common.compose.component.AddressInputWithScore import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.ButtonViewState import jp.co.soramitsu.common.compose.component.FeeInfoViewState @@ -22,7 +24,10 @@ import jp.co.soramitsu.common.compose.component.QuickAmountInput import jp.co.soramitsu.common.compose.component.SelectorState import jp.co.soramitsu.common.compose.component.ToolbarViewState import jp.co.soramitsu.common.compose.component.WarningInfoState +import jp.co.soramitsu.common.compose.theme.warningOrange import jp.co.soramitsu.common.data.network.runtime.binding.cast +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.presentation.dataOrNull import jp.co.soramitsu.common.resources.ClipboardManager import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.Event @@ -31,6 +36,7 @@ import jp.co.soramitsu.common.utils.combine import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.formatting.shortenAddress import jp.co.soramitsu.common.utils.greaterThen import jp.co.soramitsu.common.utils.isNotZero import jp.co.soramitsu.common.utils.isZero @@ -75,7 +81,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest @@ -86,6 +91,7 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -95,6 +101,7 @@ class SendSetupViewModel @Inject constructor( val savedStateHandle: SavedStateHandle, private val resourceManager: ResourceManager, private val walletInteractor: WalletInteractor, + private val nomisScoreInteractor: NomisScoreInteractor, private val polkaswapInteractor: PolkaswapInteractor, private val existentialDepositUseCase: ExistentialDepositUseCase, private val walletConstants: WalletConstants, @@ -155,12 +162,7 @@ class SendSetupViewModel @Inject constructor( } } - private val defaultAddressInputState = AddressInputState( - title = resourceManager.getString(R.string.send_to), - input = "", - image = R.drawable.ic_address_placeholder, - editable = false - ) + private val defaultAddressInputState = AddressInputWithScore.Empty(resourceManager.getString(R.string.send_to), resourceManager.getString(R.string.search_textfield_placeholder)) private val defaultAmountInputState = AmountInputViewState( tokenName = "...", @@ -353,10 +355,20 @@ class SendSetupViewModel @Inject constructor( private val phishingModelFlow = addressInputTrimmedFlow.map { walletInteractor.getPhishingInfo(it) } + private val nomisScore = addressInputTrimmedFlow.transform { + if(it.isEmpty()) { + return@transform + } + emit(LoadingState.Loading()) + val score = nomisScoreInteractor.getNomisScore(it) + emit(LoadingState.Loaded(score)) + }.stateIn(viewModelScope, SharingStarted.Eagerly, LoadingState.Loading()) + private val warningInfoStateFlow = combine( phishingModelFlow, - isWarningExpanded - ) { phishing, isExpanded -> + isWarningExpanded, + nomisScore + ) { phishing, isExpanded, nomisScoreLoadingState -> phishing?.let { WarningInfoState( message = getPhishingMessage(phishing.type), @@ -368,6 +380,17 @@ class SendSetupViewModel @Inject constructor( isExpanded = isExpanded, color = phishing.color ) + } ?: nomisScoreLoadingState.dataOrNull()?.takeIf { it.score in 0..25 }?.let { + WarningInfoState( + message = resourceManager.getString(R.string.scam_description_lowscore_text), + extras = listOf( + resourceManager.getString(R.string.username_setup_choose_title) to resourceManager.getString(R.string.scam_info_nomis_name), + resourceManager.getString(R.string.reason) to resourceManager.getString(R.string.scam_info_nomis_reason_text), + resourceManager.getString(R.string.scam_additional_stub) to resourceManager.getString(R.string.scam_info_nomis_subtype_text) + ), + isExpanded = isExpanded, + color = warningOrange + ) } } @@ -454,8 +477,7 @@ class SendSetupViewModel @Inject constructor( lockInputFlow.onEach { isInputLocked -> state.value = state.value.copy( - isInputLocked = isInputLocked, - addressInputState = state.value.addressInputState.copy(showClear = isInputLocked.not()) + isInputLocked = isInputLocked ) }.launchIn(this) @@ -493,12 +515,22 @@ class SendSetupViewModel @Inject constructor( AddressIconGenerator.SIZE_BIG ) } + val addressState = if(address.isNotEmpty()) { + (state.value.addressInputState as? AddressInputWithScore.Filled)?.copy( + address = address.shortenAddress(), + image = image + ) ?: AddressInputWithScore.Filled( + defaultAddressInputState.title, + address.shortenAddress(), + image, + nomisScore.value.dataOrNull()?.score ?: nomisScoreInteractor.getNomisScoreFromMemoryCache(address)?.score ?: NomisScoreData.LOADING_CODE + ) + } else { + defaultAddressInputState + } state.value = state.value.copy( - addressInputState = state.value.addressInputState.copy( - input = address, - image = image - ), + addressInputState = addressState, isHistoryAvailable = chain?.externalApi?.history != null ) }.launchIn(this) @@ -510,6 +542,22 @@ class SendSetupViewModel @Inject constructor( val quickInputs = quickInputsUseCase.calculateTransfersQuickInputs(chainId, asset.token.configuration.id) quickInputsStateFlow.update { quickInputs } }.launchIn(this) + + nomisScore.onEach { + val score = if(it is LoadingState.Loading) { + NomisScoreData.LOADING_CODE + } else { + val loadedState = it as LoadingState.Loaded + loadedState.data?.score ?: NomisScoreData.ERROR_CODE + } + state.update { prevState -> + if(prevState.addressInputState is AddressInputWithScore.Filled) { + prevState.copy( + addressInputState = prevState.addressInputState.copy(score = score) + ) + } else prevState + } + }.launchIn(viewModelScope) } private fun observeExistentialDeposit(showMaxInput: Boolean) { From e6062fdc8523d26d75f5a5bf698fc76d6fd0edae Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 9 Aug 2024 14:10:17 +0700 Subject: [PATCH 05/84] wip complete account settings screen with nomis Signed-off-by: Deneath --- .../main/res/navigation/bottom_nav_graph.xml | 1 - .../common/compose/component/SettingsItem.kt | 144 ++++++- .../common/compose/component/WalletItem.kt | 2 +- .../backup_wallet/BackupWalletContent.kt | 2 +- .../nomis_scoring/ScoreDetailsViewModel.kt | 2 +- .../presentation/profile/ProfileFragment.kt | 98 ++--- .../presentation/profile/ProfileScreen.kt | 110 +++++ .../presentation/profile/ProfileViewModel.kt | 180 +++++--- .../src/main/res/layout/fragment_profile.xml | 397 ------------------ .../send/setup/SendSetupViewModel.kt | 3 +- 10 files changed, 415 insertions(+), 524 deletions(-) create mode 100644 feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt delete mode 100644 feature-account-impl/src/main/res/layout/fragment_profile.xml diff --git a/app/src/main/res/navigation/bottom_nav_graph.xml b/app/src/main/res/navigation/bottom_nav_graph.xml index adc152e396..4e3be8dcda 100644 --- a/app/src/main/res/navigation/bottom_nav_graph.xml +++ b/app/src/main/res/navigation/bottom_nav_graph.xml @@ -7,7 +7,6 @@ diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/SettingsItem.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/SettingsItem.kt index 92e780e024..5afcf8af42 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/SettingsItem.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/SettingsItem.kt @@ -1,28 +1,54 @@ package jp.co.soramitsu.common.compose.component +import androidx.annotation.DrawableRes import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +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.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Switch +import androidx.compose.material.SwitchColors import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.black3 +import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.transparent +import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.utils.clickableSingle +sealed interface SettingsItemAction { + data object Transition : SettingsItemAction + + data class TransitionWithIcon(@DrawableRes val trailingIconRes: Int) : SettingsItemAction + data class Selector(val text: String) : SettingsItemAction + + data class Switch(val value: Boolean) : SettingsItemAction + +} + @Composable fun SettingsItem( + modifier: Modifier = Modifier, icon: Painter, text: String, - modifier: Modifier = Modifier, + action: SettingsItemAction = SettingsItemAction.Transition, onClick: () -> Unit = {} ) { Row( @@ -51,22 +77,114 @@ fun SettingsItem( maxLines = 1, overflow = TextOverflow.Ellipsis ) - Icon( - modifier = Modifier - .padding(end = 8.dp) - .size(24.dp), - painter = painterResource(R.drawable.ic_arrow_right_24), - contentDescription = null, - tint = MaterialTheme.customColors.white - ) + when (action) { + is SettingsItemAction.Selector -> { + B1(text = action.text, color = white) + MarginHorizontal(margin = 16.dp) + Icon( + modifier = Modifier + .padding(end = 8.dp) + .size(24.dp), + painter = painterResource(R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + } + + is SettingsItemAction.Switch -> { + FearlessSwitch(isChecked = action.value, onCheckedChange = {onClick()}) + MarginHorizontal(margin = 16.dp) + } + + SettingsItemAction.Transition -> { + Icon( + modifier = Modifier + .padding(end = 8.dp) + .size(24.dp), + painter = painterResource(R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + } + + is SettingsItemAction.TransitionWithIcon -> { + Image( + modifier = Modifier + .size(16.dp), + action.trailingIconRes + ) + MarginHorizontal(margin = 8.dp) + Icon( + modifier = Modifier + .padding(end = 8.dp) + .size(24.dp), + painter = painterResource(R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + } + } + } +} + +@Composable +fun FearlessSwitch(isChecked: Boolean, onCheckedChange: (Boolean) -> Unit) { + val trackColor = when { + isChecked -> colorAccentDark + else -> black3 + } + Switch( + colors = switchColors, + checked = isChecked, + onCheckedChange = onCheckedChange, + modifier = Modifier + .background(color = trackColor, shape = RoundedCornerShape(20.dp)) + .padding(3.dp) + .height(20.dp) + .width(36.dp) + ) +} + +val switchColors = object : SwitchColors { + @Composable + override fun thumbColor(enabled: Boolean, checked: Boolean): State { + return rememberUpdatedState(white) + } + + @Composable + override fun trackColor(enabled: Boolean, checked: Boolean): State { + return rememberUpdatedState(transparent) } } @Preview @Composable fun SettingsItemPreview() { - SettingsItem( - icon = painterResource(R.drawable.ic_settings_wallets), - text = "Item" - ) + Column { + SettingsItem( + icon = painterResource(R.drawable.ic_settings_wallets), + text = "Item" + ) + SettingsItem( + icon = painterResource(R.drawable.ic_settings_wallets), + text = "Item", + action = SettingsItemAction.Selector("Value") + ) + SettingsItem( + icon = painterResource(R.drawable.ic_settings_wallets), + text = "Item", + action = SettingsItemAction.Switch(true) + ) + SettingsItem( + icon = painterResource(R.drawable.ic_settings_wallets), + text = "Item", + action = SettingsItemAction.Switch(false) + ) + SettingsItem( + icon = painterResource(R.drawable.ic_settings_wallets), + text = "Item", + action = SettingsItemAction.TransitionWithIcon(R.drawable.ic_warning_filled) + ) + + } } diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt index 44f56e40d3..2255448ab6 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/WalletItem.kt @@ -47,7 +47,7 @@ data class WalletItemViewState( fun WalletItem( state: WalletItemViewState, onOptionsClick: ((WalletItemViewState) -> Unit)? = null, - onSelected: (WalletItemViewState) -> Unit, + onSelected: (WalletItemViewState) -> Unit = {}, onLongClick: (WalletItemViewState) -> Unit = {}, onScoreClick: (WalletItemViewState) -> Unit = {}, modifier: Modifier = Modifier diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/backup_wallet/BackupWalletContent.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/backup_wallet/BackupWalletContent.kt index 6806016716..b02ac65396 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/backup_wallet/BackupWalletContent.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/backup_wallet/BackupWalletContent.kt @@ -145,7 +145,7 @@ internal fun BackupWalletContent( } @Composable -private fun SettingsDivider( +fun SettingsDivider( modifier: Modifier = Modifier ) { Divider( diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt index 21bccc735c..96038cf184 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt @@ -83,7 +83,7 @@ class ScoreDetailsViewModel @Inject constructor( .launchIn(viewModelScope) viewModelScope.launch { - val metaAccount = accountInteractor.selectedMetaAccount() + val metaAccount = accountInteractor.getMetaAccount(metaAccountId) val address = metaAccount.ethereumAddress?.toHexString(withPrefix = true) if (address != null) { rawAddress.value = address diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt index 52e01b2a24..5499af16c7 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileFragment.kt @@ -1,96 +1,72 @@ package jp.co.soramitsu.account.impl.presentation.profile -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import jp.co.soramitsu.account.api.presentation.actions.ExternalAccountActions import jp.co.soramitsu.account.api.presentation.actions.copyAddressClicked -import jp.co.soramitsu.common.base.BaseFragment +import jp.co.soramitsu.common.base.BaseComposeFragment +import jp.co.soramitsu.common.compose.theme.black import jp.co.soramitsu.common.data.network.coingecko.FiatCurrency import jp.co.soramitsu.common.mixin.impl.observeBrowserEvents import jp.co.soramitsu.common.presentation.FiatCurrenciesChooserBottomSheetDialog import jp.co.soramitsu.common.view.bottomSheet.list.dynamic.DynamicListBottomSheet -import jp.co.soramitsu.feature_account_impl.databinding.FragmentProfileBinding @AndroidEntryPoint -class ProfileFragment : BaseFragment() { +class ProfileFragment : BaseComposeFragment() { @Inject protected lateinit var imageLoader: ImageLoader - private lateinit var binding: FragmentProfileBinding - override val viewModel: ProfileViewModel by viewModels() - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - binding = FragmentProfileBinding.inflate(inflater, container, false) - return binding.root + @OptIn(ExperimentalMaterialApi::class) + @Composable + override fun Content( + padding: PaddingValues, + scrollState: ScrollState, + modalBottomSheetState: ModalBottomSheetState + ) { + LaunchedEffect(Unit) { + subscribe(viewModel) + } + val state by viewModel.state.collectAsState() + ProfileScreen(state = state, callback = viewModel) } - override fun initViews() { - with(binding) { - accountView.setWholeClickListener { viewModel.accountActionsClicked() } - - aboutTv.setOnClickListener { viewModel.aboutClicked() } - - profileWallets.setOnClickListener { viewModel.walletsClicked() } - languageWrapper.setOnClickListener { viewModel.languagesClicked() } - changePinCodeTv.setOnClickListener { viewModel.changePinCodeClicked() } - profileCurrency.setOnClickListener { viewModel.currencyClicked() } - profileExperimentalFeatures.setOnClickListener { viewModel.onExperimentalClicked() } - polkaswapDisclaimerTv.setOnClickListener { viewModel.polkaswapDisclaimerClicked() } - profileSoraCard.setOnClickListener { viewModel.onSoraCardClicked() } - profileWalletConnect.setOnClickListener { viewModel.onWalletConnectClick() } - nomisMultichainScoreContainer.setOnClickListener { viewModel.onNomisMultichainScoreContainerClick() } - - viewModel.hasChainsWithNoAccountFlow.observe { - missingAccountsIcon.isVisible = it - } - } + @Composable + override fun Background() { + Box(modifier = Modifier.fillMaxSize().background(black)) } - override fun subscribe(viewModel: ProfileViewModel) { + fun subscribe(viewModel: ProfileViewModel) { observeBrowserEvents(viewModel) - viewModel.selectedAccountLiveData.observe { account -> - account.name.let(binding.accountView::setTitle) - } - - viewModel.accountIconLiveData.observe { - binding.accountView.setAccountIcon(it.image) - } - - viewModel.selectedLanguageLiveData.observe { - binding.selectedLanguageTv.text = it.displayName - } - viewModel.showExternalActionsEvent.observeEvent(::showAccountActions) - viewModel.totalBalanceLiveData.observe { - binding.accountView.setText(it) - } - viewModel.showFiatChooser.observeEvent(::showFiatChooser) - - viewModel.selectedFiatLiveData.observe(binding.selectedCurrencyTv::setText) - - viewModel.nomisMultichainScoreEnabledFlow.observe { - binding.nomisMultichainScoreSwitch.isChecked = it - } } private fun showFiatChooser(payload: DynamicListBottomSheet.Payload) { - FiatCurrenciesChooserBottomSheetDialog(requireContext(), imageLoader, payload, viewModel::onFiatSelected).show() + FiatCurrenciesChooserBottomSheetDialog( + requireContext(), + imageLoader, + payload, + viewModel::onFiatSelected + ).show() } private fun showAccountActions(payload: ExternalAccountActions.Payload) { diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt new file mode 100644 index 0000000000..d98696e3fb --- /dev/null +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt @@ -0,0 +1,110 @@ +package jp.co.soramitsu.account.impl.presentation.profile + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import jp.co.soramitsu.account.impl.presentation.backup_wallet.SettingsDivider +import jp.co.soramitsu.common.compose.component.ChangeBalanceViewState +import jp.co.soramitsu.common.compose.component.H1 +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.SettingsItem +import jp.co.soramitsu.common.compose.component.SettingsItemAction +import jp.co.soramitsu.common.compose.component.WalletItem +import jp.co.soramitsu.common.compose.component.WalletItemViewState +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme +import jp.co.soramitsu.feature_account_impl.R + +data class ProfileScreenState( + val walletState: WalletItemViewState, + val walletsItemAction: SettingsItemAction = SettingsItemAction.Transition, + val currency: String, + val language: String, + val nomisChecked: Boolean +) + +interface ProfileScreenInterface { + fun onWalletOptionsClick(item: WalletItemViewState) + fun walletsClicked() + + fun onWalletConnectClick() + fun onSoraCardClicked() + fun currencyClicked() + fun languagesClicked() + + fun onNomisMultichainScoreContainerClick() + fun polkaswapDisclaimerClicked() + fun changePinCodeClicked() + + fun aboutClicked() +} + +@Composable +fun ProfileScreen(state: ProfileScreenState, callback: ProfileScreenInterface) { + Column { + MarginVertical(margin = 16.dp) + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + H1(text = stringResource(R.string.profile_settings_title)) + MarginVertical(margin = 16.dp) + WalletItem(state = state.walletState, onOptionsClick = callback::onWalletOptionsClick) + } + MarginVertical(margin = 16.dp) + SettingsItem(icon = painterResource(R.drawable.ic_settings_wallets), text = stringResource(R.string.profile_wallets_title), action = state.walletsItemAction, onClick = callback::walletsClicked) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_wallet_connect), text = stringResource(R.string.profile_walletconnect_title), onClick = callback::onWalletConnectClick) + SettingsDivider() +// SettingsItem(icon = painterResource(R.drawable.ic_card), text = stringResource(R.string.profile_soracard_title), onClick = callback::onSoraCardClicked) + SettingsItem(icon = painterResource(R.drawable.ic_dollar_circle), text = stringResource(R.string.common_currency), action = SettingsItemAction.Selector(state.currency), onClick = callback::currencyClicked) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_language), text = stringResource(R.string.profile_language_title), action = SettingsItemAction.Selector(state.language), onClick = callback::languagesClicked) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_score_star_full_24_pink), text = stringResource(R.string.profile_account_score_title), action = SettingsItemAction.Switch(state.nomisChecked), onClick = callback::onNomisMultichainScoreContainerClick) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_polkaswap_logo), text = stringResource(R.string.polkaswap_disclaimer_settings_item), onClick = callback::polkaswapDisclaimerClicked) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_pin_24), text = stringResource(R.string.profile_pincode_change_title), onClick = callback::changePinCodeClicked) + SettingsDivider() + SettingsItem(icon = painterResource(R.drawable.ic_info_primary_24), text = stringResource(R.string.about_title), onClick = callback::aboutClicked) + } +} + +@Composable +@Preview +fun ProfileScreenPreview() { + val state = ProfileScreenState( + WalletItemViewState( + id = 111, + balance = "44400.3", + assetSymbol = "$", + title = "My Wallet", + walletIcon = jp.co.soramitsu.common.R.drawable.ic_wallet, + isSelected = false, + changeBalanceViewState = ChangeBalanceViewState( + percentChange = "+5.67%", + fiatChange = "$2345.32" + ), + score = 50 + ), + currency = "USD", + language = "ENG", + nomisChecked = true, + ) + FearlessAppTheme { + ProfileScreen(state, object : ProfileScreenInterface { + override fun onWalletOptionsClick(item: WalletItemViewState) = Unit + override fun walletsClicked() = Unit + override fun onWalletConnectClick() = Unit + override fun onSoraCardClicked() = Unit + override fun currencyClicked() = Unit + override fun languagesClicked() = Unit + override fun onNomisMultichainScoreContainerClick() = Unit + override fun polkaswapDisclaimerClicked() = Unit + override fun changePinCodeClicked() = Unit + override fun aboutClicked() = Unit + }) + } +} \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt index aabc035bbf..ae38ed3fd5 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt @@ -2,8 +2,6 @@ package jp.co.soramitsu.account.impl.presentation.profile import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.liveData -import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -19,17 +17,22 @@ import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.address.AddressModel import jp.co.soramitsu.common.address.createAddressModel import jp.co.soramitsu.common.base.BaseViewModel +import jp.co.soramitsu.common.compose.component.ChangeBalanceViewState +import jp.co.soramitsu.common.compose.component.SettingsItemAction +import jp.co.soramitsu.common.compose.component.WalletItemViewState import jp.co.soramitsu.common.data.network.coingecko.FiatChooserEvent import jp.co.soramitsu.common.data.network.coingecko.FiatCurrency import jp.co.soramitsu.common.domain.GetAvailableFiatCurrencies import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.formatAsChange import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.view.bottomSheet.list.dynamic.DynamicListBottomSheet +import jp.co.soramitsu.feature_account_impl.R import jp.co.soramitsu.soracard.api.domain.SoraCardInteractor import jp.co.soramitsu.soracard.impl.presentation.SoraCardItemViewState -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull @@ -37,7 +40,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch private const val AVATAR_SIZE_DP = 32 @@ -45,7 +49,7 @@ private const val AVATAR_SIZE_DP = 32 @HiltViewModel class ProfileViewModel @Inject constructor( private val interactor: AccountInteractor, - private val accountDetailsInteractor: AccountDetailsInteractor, + accountDetailsInteractor: AccountDetailsInteractor, private val nomisScoreInteractor: NomisScoreInteractor, private val soraCardInteractor: SoraCardInteractor, private val router: AccountRouter, @@ -54,72 +58,152 @@ class ProfileViewModel @Inject constructor( getTotalBalance: TotalBalanceUseCase, private val getAvailableFiatCurrencies: GetAvailableFiatCurrencies, private val selectedFiat: SelectedFiat, - private val resourceManager: ResourceManager -) : BaseViewModel(), ExternalAccountActions by externalAccountActions { + resourceManager: ResourceManager +) : BaseViewModel(), ProfileScreenInterface, ExternalAccountActions by externalAccountActions { - val totalBalanceLiveData = combine(getTotalBalance.observe(), selectedFiat.flow()) { balance, fiat -> - val selectedFiatSymbol = getAvailableFiatCurrencies[fiat]?.symbol - balance.balance.formatFiat(selectedFiatSymbol ?: balance.fiatSymbol) - }.asLiveData() + private val selectedAccountFlow: SharedFlow = + interactor.selectedMetaAccountFlow().shareIn(viewModelScope, SharingStarted.Eagerly) - val selectedAccountLiveData: LiveData = interactor.selectedMetaAccountFlow().asLiveData() - - val accountIconLiveData: LiveData = interactor.polkadotAddressForSelectedAccountFlow() - .map { createIcon(it) } - .asLiveData() - - val selectedLanguageLiveData = liveData { - val language = interactor.getSelectedLanguage() - - emit(mapLanguageToLanguageModel(language)) + private val accountIconFlow = selectedAccountFlow.map { + addressIconGenerator.createAddressIcon( + it.substrateAccountId, + AddressIconGenerator.SIZE_BIG + ) } private val _showFiatChooser = MutableLiveData() val showFiatChooser: LiveData = _showFiatChooser - val selectedFiatLiveData: LiveData = selectedFiat.flow().asLiveData().map { it.uppercase() } - - val hasChainsWithNoAccountFlow = accountDetailsInteractor.hasChainsWithNoAccount() - .stateIn(this, SharingStarted.Eagerly, false) - - private val _nomisMultichainScoreEnabledFlow = MutableStateFlow(nomisScoreInteractor.nomisMultichainScoreEnabled) - val nomisMultichainScoreEnabledFlow: Flow = _nomisMultichainScoreEnabledFlow - -// private val soraCardState = soraCardInteractor.subscribeSoraCardInfo().map { + // private val soraCardState = soraCardInteractor.subscribeSoraCardInfo().map { // val kycStatus = it?.kycStatus?.let(::mapKycStatus) // SoraCardItemViewState(kycStatus, it, null, true) // } private val soraCardState = flowOf(SoraCardItemViewState()) + private val defaultWalletItemViewState = WalletItemViewState( + id = 0, + balance = null, + assetSymbol = null, + changeBalanceViewState = null, + title = "", + walletIcon = resourceManager.getDrawable(R.drawable.ic_wallet), + isSelected = false, + additionalMetadata = "", + score = null + ) + + val state: MutableStateFlow = MutableStateFlow( + ProfileScreenState( + walletState = defaultWalletItemViewState, + walletsItemAction = SettingsItemAction.Transition, + currency = selectedFiat.get(), + language = "", + nomisChecked = true + ) + ) + init { + combine(getTotalBalance.observe(), selectedFiat.flow()) { balance, fiat -> + val selectedFiatSymbol = getAvailableFiatCurrencies[fiat]?.symbol + val formattedBalance = + balance.balance.formatFiat(selectedFiatSymbol ?: balance.fiatSymbol) + + state.update { prevState -> + val newWalletState = prevState.walletState.copy( + balance = formattedBalance, + changeBalanceViewState = ChangeBalanceViewState( + percentChange = balance.rateChange?.formatAsChange().orEmpty(), + fiatChange = balance.balanceChange.abs().formatFiat(balance.fiatSymbol) + ) + ) + prevState.copy( + walletState = newWalletState, + currency = fiat.uppercase(), + ) + } + }.launchIn(viewModelScope) + + selectedAccountFlow + .onEach { account -> + state.update { prevState -> + val newWalletState = prevState.walletState.copy( + id = account.id, + title = account.name + ) + prevState.copy(walletState = newWalletState) + } + }.launchIn(viewModelScope) + + accountIconFlow.onEach { icon -> + state.update { prevState -> + val newWalletState = prevState.walletState.copy( + walletIcon = icon + ) + prevState.copy(walletState = newWalletState) + } + + }.launchIn(viewModelScope) + nomisScoreInteractor.observeNomisMultichainScoreEnabled() .onEach { - _nomisMultichainScoreEnabledFlow.value = it + state.update { prev -> + prev.copy(nomisChecked = it) + } } .launchIn(viewModelScope) + + nomisScoreInteractor.observeCurrentAccountScore().onEach { + state.update { prevState -> + val newWalletState = prevState.walletState.copy( + score = it?.score + ) + prevState.copy(walletState = newWalletState) + } + }.launchIn(viewModelScope) + + accountDetailsInteractor.hasChainsWithNoAccount() + .onEach { hasChainsWithNoAccount -> + state.update { prevState -> + prevState.copy( + walletsItemAction = + if (hasChainsWithNoAccount) + SettingsItemAction.TransitionWithIcon(R.drawable.ic_status_warning_16) + else + SettingsItemAction.Transition + ) + } + } + .launchIn(viewModelScope) + + viewModelScope.launch { + val language = interactor.getSelectedLanguage() + val mapped = mapLanguageToLanguageModel(language) + state.update { prevState -> + prevState.copy(language = mapped.displayName) + } + } } - fun aboutClicked() { + override fun aboutClicked() { router.openAboutScreen() } - fun walletsClicked() { + override fun onWalletOptionsClick(item: WalletItemViewState) { + router.openAccountDetails(item.id) + } + + override fun walletsClicked() { router.openSelectWallet() } - fun languagesClicked() { + override fun languagesClicked() { router.openLanguages() } - fun changePinCodeClicked() { + override fun changePinCodeClicked() { router.openChangePinCode() } - fun accountActionsClicked() { - val account = selectedAccountLiveData.value ?: return - router.openAccountDetails(account.id) - } - private suspend fun createIcon(accountAddress: String): AddressModel { return addressIconGenerator.createAddressModel(accountAddress, AVATAR_SIZE_DP) } @@ -128,13 +212,14 @@ class ProfileViewModel @Inject constructor( router.openBeacon(qrContent) } - fun currencyClicked() { + override fun currencyClicked() { viewModelScope.launch { val currencies = getAvailableFiatCurrencies() if (currencies.isEmpty()) return@launch val selected = selectedFiat.get() val selectedItem = currencies.first { it.id == selected } - _showFiatChooser.value = FiatChooserEvent(DynamicListBottomSheet.Payload(currencies, selectedItem)) + _showFiatChooser.value = + FiatChooserEvent(DynamicListBottomSheet.Payload(currencies, selectedItem)) } } @@ -148,11 +233,11 @@ class ProfileViewModel @Inject constructor( router.openExperimentalFeatures() } - fun polkaswapDisclaimerClicked() { + override fun polkaswapDisclaimerClicked() { router.openPolkaswapDisclaimerFromProfile() } - fun onSoraCardClicked() { + override fun onSoraCardClicked() { launch { val soraCardState: SoraCardItemViewState? = soraCardState.firstOrNull() if (soraCardState?.kycStatus == null) { @@ -166,11 +251,12 @@ class ProfileViewModel @Inject constructor( private fun onSoraCardStatusClicked() { } - fun onWalletConnectClick() { + override fun onWalletConnectClick() { router.openConnectionsScreen() } - fun onNomisMultichainScoreContainerClick() { - nomisScoreInteractor.nomisMultichainScoreEnabled = !nomisScoreInteractor.nomisMultichainScoreEnabled + override fun onNomisMultichainScoreContainerClick() { + nomisScoreInteractor.nomisMultichainScoreEnabled = + !nomisScoreInteractor.nomisMultichainScoreEnabled } } diff --git a/feature-account-impl/src/main/res/layout/fragment_profile.xml b/feature-account-impl/src/main/res/layout/fragment_profile.xml deleted file mode 100644 index d11ad07980..0000000000 --- a/feature-account-impl/src/main/res/layout/fragment_profile.xml +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt index 5f06eb915b..17037e5c35 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt @@ -547,8 +547,7 @@ class SendSetupViewModel @Inject constructor( val score = if(it is LoadingState.Loading) { NomisScoreData.LOADING_CODE } else { - val loadedState = it as LoadingState.Loaded - loadedState.data?.score ?: NomisScoreData.ERROR_CODE + it.dataOrNull()?.score } state.update { prevState -> if(prevState.addressInputState is AddressInputWithScore.Filled) { From b7eeb8663a04e533c97bd4b9ae77a2bbf0b09c9e Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 12 Aug 2024 12:03:55 +0500 Subject: [PATCH 06/84] ui improve --- .../nomis_scoring/ScoreDetailsFragment.kt | 7 ++++++- .../nomis_scoring/ScoreDetailsScreen.kt | 21 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt index e86a8be995..47e6ab635f 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsFragment.kt @@ -1,8 +1,11 @@ package jp.co.soramitsu.account.impl.presentation.nomis_scoring import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -25,7 +28,9 @@ class ScoreDetailsFragment : BaseComposeBottomSheetDialogFragment Error() + is ScoreDetailsViewState.Error -> { + Box(modifier = Modifier.weight(1f)) { + Error() + } + } + ScoreDetailsViewState.Loading -> ScoresInfoTable(null) is ScoreDetailsViewState.Success -> ScoresInfoTable(state = info.data) } @@ -168,6 +174,8 @@ private fun Error() { @Composable private fun ScoresInfoTable(state: ScoreInfoState?) { InfoTable( + modifier = Modifier + .fillMaxSize(), items = listOf( TitleValueViewState( title = stringResource(id = R.string.account_stats_updated_title), @@ -291,7 +299,8 @@ private fun ScoreDetailsScreenPreview() { val successState = ScoreDetailsViewState.Success(info) val state = ScoreDetailsScreenState( "Blue Bird 0x23f4g34nign234ij134f0134ifm13i4f134f", - info = ScoreDetailsViewState.Error +// info = ScoreDetailsViewState.Error + info = successState ) ScoreDetailsContent(state, object : ScoreDetailsScreenCallback { override fun onBackClicked() = Unit From 4ab7e97aa89f5e3a365882c7c112418a48ccae4d Mon Sep 17 00:00:00 2001 From: Deneath Date: Tue, 13 Aug 2024 17:42:26 +0700 Subject: [PATCH 07/84] some fixes Signed-off-by: Deneath --- build.gradle | 4 +- .../account/impl/domain/WalletSyncService.kt | 49 +++++++++++++------ .../nomis_scoring/ScoreDetailsViewModel.kt | 12 ++--- gradle/libs.versions.toml | 8 +-- gradle/wrapper/gradle-wrapper.properties | 6 +-- 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 94f390635e..9d0b5f386c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,10 +10,10 @@ buildscript { // SDK and tools compileSdkVersion = 34 - minSdkVersion = 24 + minSdkVersion = 26 targetSdkVersion = 34 - composeCompilerVersion = '1.5.11' + composeCompilerVersion = '1.5.14' withoutBasic = { exclude group: 'jp.co.soramitsu.xnetworking', module: 'basic' } withoutJna = { exclude group: 'net.java.dev.jna' } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt index a16979df59..3418e8d7d1 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt @@ -1,7 +1,6 @@ package jp.co.soramitsu.account.impl.domain import android.util.Log -import java.math.BigInteger import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.account.impl.data.mappers.mapMetaAccountLocalToMetaAccount @@ -9,6 +8,7 @@ import jp.co.soramitsu.account.impl.data.mappers.toLocal import jp.co.soramitsu.common.data.network.nomis.NomisApi import jp.co.soramitsu.common.data.network.runtime.binding.AssetBalance import jp.co.soramitsu.common.data.network.runtime.binding.toAssetBalance +import jp.co.soramitsu.common.utils.ethereumAddressFromPublicKey import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.positiveOrNull import jp.co.soramitsu.core.models.ChainAssetType @@ -18,10 +18,13 @@ import jp.co.soramitsu.coredb.dao.MetaAccountDao import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.model.AssetLocal import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal -import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal +import jp.co.soramitsu.coredb.model.chain.RelationJoinedMetaAccountInfo import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.chain.model.BSCChainId import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ethereumChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.polygonChainId import jp.co.soramitsu.runtime.multiNetwork.connection.EvmConnectionStatus import jp.co.soramitsu.runtime.storage.source.RemoteStorageSource import jp.co.soramitsu.shared_utils.extensions.toHexString @@ -47,6 +50,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import java.math.BigInteger private const val TAG = "WalletSyncService" class WalletSyncService( @@ -549,9 +553,15 @@ class WalletSyncService( private fun observeNomisScores() { var syncJob: Job? = null - metaAccountDao.metaAccountsFlow() - .distinctUntilChangedBy { it.size } - .map { accounts -> + val supportedChains = setOf( + ethereumChainId, + BSCChainId, + polygonChainId, + ) + + metaAccountDao.observeJoinedMetaAccountsInfo() + .distinctUntilChangedBy { it.size + it.map { info -> info.chainAccounts }.flatten().size } + .map { metaAccountInfo -> val existingScores = nomisScoresDao.getScores() val currentTime = System.currentTimeMillis() val twelveHoursMillis = 12 * 60 * 60 * 1000L @@ -559,14 +569,14 @@ class WalletSyncService( existingScores.filter { currentTime - it.updated > twelveHoursMillis } .map { it.metaId } - val metaAccounts = accounts.asSequence() - .filter { it.ethereumAddress != null } + val metaAccounts = metaAccountInfo.asSequence() + .filter { info -> info.metaAccount.ethereumAddress != null || info.chainAccounts.any { it.chainId in supportedChains } } val newAccounts = - metaAccounts.filter { it.id !in existingScores.map { score -> score.metaId } } + metaAccounts.filter { it.metaAccount.id !in existingScores.map { score -> score.metaId } } - val accountsToUpdate = accounts.asSequence() - .filter { it.id in existingScoresToUpdate } + val accountsToUpdate = metaAccountInfo.asSequence() + .filter { it.metaAccount.id in existingScoresToUpdate } (newAccounts + accountsToUpdate).toSet() } @@ -579,17 +589,24 @@ class WalletSyncService( .launchIn(nomisUpdateScope) } - private suspend fun syncNomisScores(vararg metaAccount: MetaAccountLocal) { + private suspend fun syncNomisScores(vararg metaAccount: RelationJoinedMetaAccountInfo) { return coroutineScope { - metaAccount.onEach { account -> + val supportedChains = setOf( + ethereumChainId, + BSCChainId, + polygonChainId, + ) + metaAccount.onEach { accountInfo -> launch { - nomisScoresDao.insert(NomisWalletScoreLocal.loading(account.id)) + val id = accountInfo.metaAccount.id + nomisScoresDao.insert(NomisWalletScoreLocal.loading(id)) runCatching { - nomisApi.getNomisScore(account.ethereumAddress!!.toHexString(true)) + val address = accountInfo.metaAccount.ethereumAddress ?: accountInfo.chainAccounts.firstOrNull { it.chainId in supportedChains }?.publicKey?.ethereumAddressFromPublicKey() + nomisApi.getNomisScore(address!!.toHexString(true)) }.onSuccess { response -> - nomisScoresDao.insert(response.toLocal(account.id)) + nomisScoresDao.insert(response.toLocal(id)) }.onFailure { - nomisScoresDao.insert(NomisWalletScoreLocal.error(account.id)) + nomisScoresDao.insert(NomisWalletScoreLocal.error(id)) } } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt index 96038cf184..84f695af50 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt @@ -3,10 +3,6 @@ package jp.co.soramitsu.account.impl.presentation.nomis_scoring import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.model.NomisScoreData @@ -23,6 +19,10 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject @HiltViewModel class ScoreDetailsViewModel @Inject constructor( @@ -84,10 +84,10 @@ class ScoreDetailsViewModel @Inject constructor( viewModelScope.launch { val metaAccount = accountInteractor.getMetaAccount(metaAccountId) - val address = metaAccount.ethereumAddress?.toHexString(withPrefix = true) + val address = (metaAccount.ethereumAddress ?: metaAccount.chainAccounts.values.firstOrNull { it.chain?.isEthereumChain == true || it.chain?.isEthereumBased == true}?.publicKey)?.toHexString(withPrefix = true) if (address != null) { rawAddress.value = address - state.update { prevState -> prevState.copy(address = "${metaAccount.name} $address") } + state.update { prevState -> prevState.copy(address = address) } } else { state.update { prevState -> prevState.copy(info = ScoreDetailsViewState.Error) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a785d2fc6..bd1b2fe9a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanistVersion = "0.34.0" activityCompose = "1.8.2" -android_plugin = "8.3.2" +android_plugin = "8.6.0-rc01" appcompat = "1.6.1" architectureComponentVersion = "2.7.0" beaconVersion = "3.2.4" @@ -10,7 +10,7 @@ bouncyCastleVersion = "1.78" cardViewVersion = "1.0.0" coilVersion = "2.6.0" compose = "1.6.5" -composeCompiler = "1.5.11" +composeCompiler = "1.5.14" composeShimmer = "1.0.4" composeThemeAdapter = "1.2.1" constraintlayoutComposeVersion = "1.0.1" @@ -30,7 +30,7 @@ insetterVersion = "0.5.0" jna = "5.14.0" junit = "4.13.2" junitVersion = "1.1.5" -kotlin = "1.9.23" +kotlin = "1.9.24" kotlinxSerializationjson = "1.6.3" legacySupportV4 = "1.0.0" material = "1.11.0" @@ -52,7 +52,7 @@ runner = "1.5.2" sharedFeaturesVersion = "1.1.1.35-FLW" shimmerVersion = "0.5.0" sonarqubeGradlePlugin = "3.3" -soraUiCore = "0.2.22" +soraUiCore = "0.2.32" storiesVersion = "3.0.1" walletconnectBom = "1.31.4" web3j = "4.8.8-android" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a781b73d47..8bede77e6f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Aug 04 19:19:31 YEKT 2022 +#Tue Aug 13 16:04:52 GMT+07:00 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From efb294e7781431e344cad26497ecc82f13042cd6 Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 16 Aug 2024 14:25:06 +0700 Subject: [PATCH 08/84] fix all nomis bugs: FLW-4864, FLW-4863, FLW-4861 Signed-off-by: Deneath --- .../soramitsu/common/compose/component/Toolbar.kt | 2 +- .../account/impl/presentation/AccountRouter.kt | 2 ++ .../nomis_scoring/ScoreDetailsScreen.kt | 13 ++----------- .../nomis_scoring/ScoreDetailsViewModel.kt | 4 ---- .../impl/presentation/profile/ProfileScreen.kt | 4 +++- .../impl/presentation/profile/ProfileViewModel.kt | 6 +++++- .../balance/optionswallet/OptionsWalletContent.kt | 11 +++++++++++ .../balance/optionswallet/OptionsWalletViewModel.kt | 6 +++++- 8 files changed, 29 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt index 46a96e3ae8..9234e11d16 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/Toolbar.kt @@ -368,7 +368,7 @@ fun Toolbar(state: ToolbarViewState, modifier: Modifier = Modifier, onNavigation modifier = Modifier.weight(1f) ) { ToolbarHomeIcon( - state = ToolbarHomeIconState.Navigation(navigationIcon = navIcon), + state = ToolbarHomeIconState.Navigation(navigationIcon = navIcon, tint = white), onClick = onNavigationClick ) } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt index 79f28b8e8d..9957187ca7 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt @@ -105,4 +105,6 @@ interface AccountRouter : SecureRouter { fun openImportRemoteWalletDialog() fun openConnectionsScreen() + + fun openScoreDetailsScreen(metaId: Long) } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt index 0d7736c527..282803141f 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsScreen.kt @@ -33,7 +33,6 @@ import jp.co.soramitsu.common.compose.component.H1 import jp.co.soramitsu.common.compose.component.Image import jp.co.soramitsu.common.compose.component.InfoTable import jp.co.soramitsu.common.compose.component.MarginVertical -import jp.co.soramitsu.common.compose.component.MenuIconItem import jp.co.soramitsu.common.compose.component.TitleValueViewState import jp.co.soramitsu.common.compose.component.Toolbar import jp.co.soramitsu.common.compose.component.ToolbarViewState @@ -72,7 +71,6 @@ data class ScoreInfoState( ) interface ScoreDetailsScreenCallback { - fun onBackClicked() fun onCloseClicked() fun onCopyAddressClicked() } @@ -90,15 +88,9 @@ fun ScoreDetailsContent( Toolbar( state = ToolbarViewState( stringResource(id = R.string.account_stats_title), - R.drawable.ic_arrow_back_24dp, - listOf( - MenuIconItem( - icon = R.drawable.ic_cross_24, - onClick = callback::onCloseClicked - ) - ) + R.drawable.ic_cross_24 ), - onNavigationClick = callback::onBackClicked + onNavigationClick = callback::onCloseClicked ) Column( modifier = Modifier @@ -303,7 +295,6 @@ private fun ScoreDetailsScreenPreview() { info = successState ) ScoreDetailsContent(state, object : ScoreDetailsScreenCallback { - override fun onBackClicked() = Unit override fun onCloseClicked() = Unit override fun onCopyAddressClicked() = Unit }) diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt index 84f695af50..33c684c82e 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/nomis_scoring/ScoreDetailsViewModel.kt @@ -94,10 +94,6 @@ class ScoreDetailsViewModel @Inject constructor( } } - override fun onBackClicked() { - router.back() - } - override fun onCloseClicked() { router.back() } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt index d98696e3fb..41cc2b5349 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileScreen.kt @@ -41,6 +41,7 @@ interface ProfileScreenInterface { fun changePinCodeClicked() fun aboutClicked() + fun onScoreClick(item: WalletItemViewState) } @Composable @@ -50,7 +51,7 @@ fun ProfileScreen(state: ProfileScreenState, callback: ProfileScreenInterface) { Column(modifier = Modifier.padding(horizontal = 16.dp)) { H1(text = stringResource(R.string.profile_settings_title)) MarginVertical(margin = 16.dp) - WalletItem(state = state.walletState, onOptionsClick = callback::onWalletOptionsClick) + WalletItem(state = state.walletState, onOptionsClick = callback::onWalletOptionsClick, onScoreClick = callback::onScoreClick) } MarginVertical(margin = 16.dp) SettingsItem(icon = painterResource(R.drawable.ic_settings_wallets), text = stringResource(R.string.profile_wallets_title), action = state.walletsItemAction, onClick = callback::walletsClicked) @@ -105,6 +106,7 @@ fun ProfileScreenPreview() { override fun polkaswapDisclaimerClicked() = Unit override fun changePinCodeClicked() = Unit override fun aboutClicked() = Unit + override fun onScoreClick(item: WalletItemViewState) = Unit }) } } \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt index ae38ed3fd5..fc7ea93ab1 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/profile/ProfileViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase @@ -43,6 +42,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject private const val AVATAR_SIZE_DP = 32 @@ -259,4 +259,8 @@ class ProfileViewModel @Inject constructor( nomisScoreInteractor.nomisMultichainScoreEnabled = !nomisScoreInteractor.nomisMultichainScoreEnabled } + + override fun onScoreClick(item: WalletItemViewState) { + router.openScoreDetailsScreen(item.id) + } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt index 0c56e96a2f..5495e50e82 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt @@ -42,6 +42,8 @@ interface OptionsWalletCallback { fun onDeleteWalletClick() fun onCloseClick() + + fun onShowWalletScoreClick() } @Composable @@ -104,6 +106,14 @@ fun OptionsWalletContent( text = stringResource(id = R.string.change_wallet_name), onClick = callback::onChangeWalletNameClick ) + MarginVertical(margin = 12.dp) + GrayButton( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + text = stringResource(id = R.string.account_stats_wallet_option_title), + onClick = callback::onShowWalletScoreClick + ) if (!state.isSelected) { MarginVertical(margin = 12.dp) TextButton( @@ -134,6 +144,7 @@ private fun OptionsWalletScreenPreview() { override fun onBackupWalletClick() {} override fun onDeleteWalletClick() {} override fun onCloseClick() {} + override fun onShowWalletScoreClick() {} } ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt index a681bbf14b..73b8084ab1 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.utils.Event @@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class OptionsWalletViewModel @Inject constructor( @@ -76,4 +76,8 @@ class OptionsWalletViewModel @Inject constructor( override fun onBackupWalletClick() { router.openBackupWalletScreen(walletId) } + + override fun onShowWalletScoreClick() { + router.openScoreDetailsScreen(walletId) + } } From 5e6eb0dd8d74da660011fdec6770690cd62b4252 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 6 Aug 2024 19:57:03 +0500 Subject: [PATCH 09/84] FLW-4790 new indexers for EVM --- .../success/presentation/SuccessViewModel.kt | 1 + .../data/historySource/AtletaHistorySource.kt | 87 +++++++++++++++++++ .../historySource/FiveireHistorySource.kt | 85 ++++++++++++++++++ .../historySource/HistorySourceProvider.kt | 5 ++ .../data/historySource/KlaytnHistorySource.kt | 64 ++++++++++++++ .../historySource/VicscanHistorySource.kt | 65 ++++++++++++++ .../data/historySource/ZchainHistorySource.kt | 62 +++++++++++++ .../model/response/AtletaHistoryResponse.kt | 29 +++++++ .../model/response/FiveireHistoryResponse.kt | 37 ++++++++ .../model/response/KlaytnHistoryResponse.kt | 25 ++++++ .../model/response/VicscanHistoryResponse.kt | 25 ++++++ .../model/response/ZchainHistoryResponse.kt | 40 +++++++++ .../network/subquery/OperationsHistoryApi.kt | 39 +++++++++ .../impl/data/repository/HistoryRepository.kt | 4 +- .../runtime/multiNetwork/chain/Mappers.kt | 6 ++ .../runtime/multiNetwork/chain/model/Chain.kt | 6 +- 16 files changed, 575 insertions(+), 5 deletions(-) create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/AtletaHistoryResponse.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/KlaytnHistoryResponse.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/VicscanHistoryResponse.kt create mode 100644 feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ZchainHistoryResponse.kt diff --git a/feature-success-impl/src/main/kotlin/jp/co/soramitsu/success/presentation/SuccessViewModel.kt b/feature-success-impl/src/main/kotlin/jp/co/soramitsu/success/presentation/SuccessViewModel.kt index 42931011f5..451c630dd7 100644 --- a/feature-success-impl/src/main/kotlin/jp/co/soramitsu/success/presentation/SuccessViewModel.kt +++ b/feature-success-impl/src/main/kotlin/jp/co/soramitsu/success/presentation/SuccessViewModel.kt @@ -69,6 +69,7 @@ class SuccessViewModel @Inject constructor( } Chain.Explorer.Type.OKLINK, Chain.Explorer.Type.ETHERSCAN, + Chain.Explorer.Type.KLAYTN, Chain.Explorer.Type.ZETA -> { BlockExplorerUrlBuilder(explorerItem.url, explorerItem.types).build(BlockExplorerUrlBuilder.Type.TX, operationHash) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt new file mode 100644 index 0000000000..8d77943a6a --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt @@ -0,0 +1,87 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import android.os.Build +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.Locale +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class AtletaHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val historyUrl: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val page = cursor?.toInt() ?: 1 + val responseResult = + runCatching { + val url = historyUrl.replace("{address}", accountAddress) + walletOperationsApi.getAtletaOperationsHistory( + url = url + ) + } + + return responseResult.fold(onSuccess = { + val operations = it.items.map { element -> + val status = when (element.result) { + "success" -> Operation.Status.COMPLETED + "error" -> Operation.Status.FAILED + else -> Operation.Status.COMPLETED + } + Operation( + id = element.hash, + address = accountAddress, + time = parseTimeToMillis(element.timestamp), + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = element.hash, + myAddress = accountAddress, + amount = element.value, + receiver = element.to.hash, + sender = element.from.hash, + status = status, + fee = element.fee?.value + ) + ) + } + + val nextCursor = (page + 1).toString() + CursorPage(nextCursor, operations) + }, onFailure = { + CursorPage(null, emptyList()) + }) + + } + + private val blockScanDateFormat by lazy { + SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSX", + Locale.getDefault() + ) + } + + private fun parseTimeToMillis(timestamp: String): Long { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Instant.parse(timestamp).toEpochMilli() + } else { + try { + blockScanDateFormat.parse(timestamp)?.time ?: 0 + } catch (e: Exception) { + 0 + } + } + } +} \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt new file mode 100644 index 0000000000..7f26b5239a --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt @@ -0,0 +1,85 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import android.os.Build +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.Locale +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class FiveireHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val historyUrl: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val page = cursor?.toInt() ?: 1 + val responseResult = + runCatching { + val url = historyUrl.replace("{address}", accountAddress) + walletOperationsApi.getFiveireOperationsHistory( + url = url, + page = page, + limit = pageSize + ) + } + + return responseResult.fold(onSuccess = { + val operations = it.data.transactions.mapNotNull { element -> + if (element.toAddress.isNullOrEmpty()) return@mapNotNull null + val status = if (element.status == 1) Operation.Status.COMPLETED else Operation.Status.FAILED + Operation( + id = element.hash, + address = accountAddress, + time = parseTimeToMillis(element.createdAt), + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = element.hash, + myAddress = accountAddress, + amount = element.value, + receiver = element.toAddress.lowercase(), + sender = element.fromAddress.lowercase(), + status = status, + fee = element.gasUsed + ) + ) + } + + val nextCursor = (page + 1).toString() + CursorPage(nextCursor, operations) + }, onFailure = { + CursorPage(null, emptyList()) + }) + } + + private val blockScanDateFormat by lazy { + SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSX", + Locale.getDefault() + ) + } + + private fun parseTimeToMillis(timestamp: String): Long { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Instant.parse(timestamp).toEpochMilli() + } else { + try { + blockScanDateFormat.parse(timestamp)?.time ?: 0 + } catch (e: Exception) { + 0 + } + } + } +} \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt index 95abfdc8f0..2823a04f65 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt @@ -22,6 +22,11 @@ class HistorySourceProvider( Chain.ExternalApi.Section.Type.OKLINK -> OkLinkHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.ZETA -> ZetaHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.REEF -> ReefHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.KLAYTN -> KlaytnHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.FIVEIRE -> FiveireHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.VICSCAN -> VicscanHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.ZCHAIN -> ZchainHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.ATLETA -> AtletaHistorySource(walletOperationsApi, historyUrl) else -> null } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt new file mode 100644 index 0000000000..3333668097 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt @@ -0,0 +1,64 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import android.os.Build +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.Locale +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class KlaytnHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val historyUrl: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val page = cursor?.toInt() ?: 1 + val responseResult = + runCatching { + val url = historyUrl.replace("{address}", accountAddress) + walletOperationsApi.getKlaytnOperationsHistory( + url = url, + page = page + ) + } + + return responseResult.fold(onSuccess = { + val operations = it.result.map { element -> + val status = if (element.txStatus == 1) Operation.Status.COMPLETED else Operation.Status.FAILED + Operation( + id = element.txHash, + address = accountAddress, + time = element.createdAt, + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = element.txHash, + myAddress = accountAddress, + amount = element.amount, + receiver = element.toAddress.lowercase(), + sender = element.fromAddress.lowercase(), + status = status, + fee = element.txFee + ) + ) + } + + val nextCursor = (page + 1).toString() + CursorPage(nextCursor, operations) + }, onFailure = { + CursorPage(null, emptyList()) + }) + } +} \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt new file mode 100644 index 0000000000..1f32f87872 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt @@ -0,0 +1,65 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class VicscanHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val historyUrl: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val page = cursor?.toInt() ?: 1 + val responseResult = + runCatching { + walletOperationsApi.getVicscanOperationsHistory( + url = historyUrl, + account = accountAddress, + limit = pageSize, + offset = (page - 1) * pageSize + ) + } + + return responseResult.fold(onSuccess = { + val operations = it.data.map { element -> + val status = when (element.status) { + "success" -> Operation.Status.COMPLETED + "error" -> Operation.Status.FAILED + else -> Operation.Status.COMPLETED + } + Operation( + id = element.transactionIndex.toString(), + address = accountAddress, + time = element.timestamp, + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = element.hash, + myAddress = accountAddress, + amount = element.value, + receiver = element.to.lowercase(), + sender = element.from.lowercase(), + status = status, + fee = element.gasUsed + ) + ) + } + + val nextCursor = (page + 1).toString() + CursorPage(nextCursor, operations) + }, onFailure = { + CursorPage(null, emptyList()) + }) + } +} \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt new file mode 100644 index 0000000000..ceb1c7ff03 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt @@ -0,0 +1,62 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class ZchainHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val historyUrl: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val page = cursor?.toInt() ?: 1 + val responseResult = + runCatching { + walletOperationsApi.getZchainOperationsHistory( + url = historyUrl, + address = accountAddress, + pageSize = pageSize, + page = page + ) + } + + return responseResult.fold(onSuccess = { + val operations = it.data.map { element -> + val status = if (element.success) Operation.Status.COMPLETED else Operation.Status.FAILED + Operation( + id = element.hash, + address = accountAddress, + time = element.timestamp, + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = element.hash, + myAddress = accountAddress, + amount = element.value, + receiver = element.to.address, + sender = element.from.address, + status = status, + fee = element.gasUsed + ) + ) + } + + val nextCursor = (page + 1).toString() + CursorPage(nextCursor, operations) + }, onFailure = { + CursorPage(null, emptyList()) + }) + + } +} \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/AtletaHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/AtletaHistoryResponse.kt new file mode 100644 index 0000000000..98866289e5 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/AtletaHistoryResponse.kt @@ -0,0 +1,29 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import com.google.gson.annotations.SerializedName +import java.math.BigInteger + +data class AtletaHistoryResponse( + val items: List, + @SerializedName("next_page_params") + val nextPageParams: NextPageParams? = null +) + +data class AtletaHistoryItem( + val timestamp: String, + val fee: AtletaFee? = null, + val result: String, + val to: AtletaAddress, + val from: AtletaAddress, + val value: BigInteger, + val hash: String +) + +data class AtletaAddress( + val hash: String +) + +data class AtletaFee( + val type: String, + val value: BigInteger +) \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt new file mode 100644 index 0000000000..9ae3e03595 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt @@ -0,0 +1,37 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import com.google.gson.annotations.SerializedName +import java.math.BigInteger +import jp.co.soramitsu.common.data.network.runtime.binding.BlockNumber + +data class FiveireHistoryResponse( + val message: String, + val data: FiveireHistoryData, + val page: Int, + val total: Int +) + +data class FiveireHistoryData( + val count: Int, + val transactions: List +) + +data class FiveireHistoryItem( + val hash: String, + @SerializedName("receipt_status") + val status: Int, + @SerializedName("created_at") + val createdAt: String, + @SerializedName("block_number") + val blockNumber: BlockNumber, + @SerializedName("from_address") + val fromAddress: String, + @SerializedName("to_address") + val toAddress: String?, + val value: BigInteger, + val gas: BigInteger, + @SerializedName("receipt_cumulative_gas_used") + val gasUsed: BigInteger, + @SerializedName("gas_price") + val gasPrice: BigInteger +) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/KlaytnHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/KlaytnHistoryResponse.kt new file mode 100644 index 0000000000..efd92c55c6 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/KlaytnHistoryResponse.kt @@ -0,0 +1,25 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import java.math.BigInteger +import jp.co.soramitsu.common.data.network.runtime.binding.BlockNumber + +data class KlaytnHistoryItem( + val createdAt: Long, + val txHash: String, + val blockNumber: BlockNumber, + val fromAddress: String, + val toAddress: String, + val amount: BigInteger, + val txFee: BigInteger, + val gasLimit: BigInteger, + val gasUsed: BigInteger, + val gasPrice: BigInteger, + val txStatus: Int, +) + +data class KlaytnHistoryResponse( + val success: Boolean, + val result: List, + val page: Int, + val total: Int +) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/VicscanHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/VicscanHistoryResponse.kt new file mode 100644 index 0000000000..a0ae7fb5ca --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/VicscanHistoryResponse.kt @@ -0,0 +1,25 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import java.math.BigInteger +import jp.co.soramitsu.common.data.network.runtime.binding.BlockNumber + +data class VicscanHistoryResponse( + val data: List, + val total: Int +) + +data class VicscanHistoryItem( + val transactionIndex: BigInteger, + val status: String, + val hash: String, + val timestamp: Long, + val blockNumber: BlockNumber, + val from: String, + val to: String, + val value: BigInteger, + val gas: BigInteger, + val fee: BigInteger, + val gasUsed: BigInteger, + val gasPrice: BigInteger +) + diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ZchainHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ZchainHistoryResponse.kt new file mode 100644 index 0000000000..ba84e0cf6d --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ZchainHistoryResponse.kt @@ -0,0 +1,40 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import com.google.gson.annotations.SerializedName +import java.math.BigInteger +import jp.co.soramitsu.common.data.network.runtime.binding.BlockNumber + +data class ZchainHistoryResponse( + val data: List, + val total: Int +) + +data class ZchainHistoryItem( + @SerializedName("s") + val success: Boolean, + @SerializedName("h") + val hash: String, + @SerializedName("ti") + val timestamp: Long, + @SerializedName("bn") + val blockNumber: BlockNumber, + @SerializedName("f") + val from: ZchainHistoryAddress, + @SerializedName("f") + val to: ZchainHistoryAddress, + @SerializedName("v") + val value: BigInteger, + @SerializedName("gl") + val gas: BigInteger, + @SerializedName("tf") + val fee: BigInteger, + @SerializedName("gu") + val gasUsed: BigInteger, + @SerializedName("gp") + val gasPrice: BigInteger +) + +data class ZchainHistoryAddress( + @SerializedName("a") + val address: String +) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt index fada86b5dd..c87c652ad7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt @@ -8,12 +8,17 @@ import jp.co.soramitsu.wallet.impl.data.network.model.request.GiantsquidHistoryR import jp.co.soramitsu.wallet.impl.data.network.model.request.ReefHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.request.SubqueryHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.request.SubsquidHistoryRequest +import jp.co.soramitsu.wallet.impl.data.network.model.response.AtletaHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.EtherscanHistoryResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.FiveireHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.GiantsquidHistoryResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.KlaytnHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.OkLinkHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.ReefHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.SubqueryHistoryElementResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.SubsquidHistoryElementsConnectionResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.VicscanHistoryResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.ZchainHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.ZetaHistoryResponse import retrofit2.http.Body import retrofit2.http.GET @@ -68,6 +73,40 @@ interface OperationsHistoryApi { @Url url: String ): ZetaHistoryResponse + @GET + suspend fun getAtletaOperationsHistory( + @Url url: String + ): AtletaHistoryResponse + + @GET + suspend fun getKlaytnOperationsHistory( + @Url url: String, + @Query("page") page: Int + ): KlaytnHistoryResponse + + @GET + suspend fun getFiveireOperationsHistory( + @Url url: String, + @Query("page") page: Int, + @Query("limit") limit: Int + ): FiveireHistoryResponse + + @GET + suspend fun getZchainOperationsHistory( + @Url url: String, + @Query("a") address: String, + @Query("page") page: Int, + @Query("page_size") pageSize: Int, + ): ZchainHistoryResponse + + @GET + suspend fun getVicscanOperationsHistory( + @Url url: String, + @Query("account") account: String, + @Query("limit") limit: Int, + @Query("offset") offset: Int + ): VicscanHistoryResponse + @POST suspend fun getReefOperationsHistory( @Url url: String, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt index 8051ed131b..bbf2a11093 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/HistoryRepository.kt @@ -100,11 +100,11 @@ class HistoryRepository( getOperations(pageSize, cursor = nextCursor, filters, accountId, chain, chainAsset) }.getOrDefault(CursorPage(null, emptyList())) nextCursor = page.nextCursor - hasNextPage = nextCursor != null + hasNextPage = nextCursor != null && page.items.isNotEmpty() elements.addAll(page.map { mapOperationToOperationLocalDb( it, - OperationLocal.Source.SUBQUERY + OperationLocal.Source.BLOCKCHAIN ) }) } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt index 537f0351ca..012d3c59e4 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt @@ -34,6 +34,11 @@ private fun mapSectionTypeRemoteToSectionType(section: String) = when (section) "oklink" -> Chain.ExternalApi.Section.Type.OKLINK "zeta" -> Chain.ExternalApi.Section.Type.ZETA "reef" -> Chain.ExternalApi.Section.Type.REEF + "klaytn" -> Chain.ExternalApi.Section.Type.KLAYTN + "5ire" -> Chain.ExternalApi.Section.Type.FIVEIRE + "vicscan" -> Chain.ExternalApi.Section.Type.VICSCAN + "zchain" -> Chain.ExternalApi.Section.Type.ZCHAIN + "atleta" -> Chain.ExternalApi.Section.Type.ATLETA else -> Chain.ExternalApi.Section.Type.UNKNOWN } @@ -45,6 +50,7 @@ private fun mapExplorerTypeRemoteToExplorerType(explorer: String) = when (explor "okx explorer" -> Chain.Explorer.Type.OKLINK "zeta" -> Chain.Explorer.Type.ZETA "reef" -> Chain.Explorer.Type.REEF + "klaytn" -> Chain.Explorer.Type.KLAYTN else -> Chain.Explorer.Type.UNKNOWN } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt index 3a4d021b03..ee733a2893 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt @@ -67,16 +67,16 @@ data class Chain( ) { data class Section(val type: Type, val url: String) { enum class Type { - SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, UNKNOWN, GITHUB; + SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIVEIRE, VICSCAN, ZCHAIN, ATLETA, UNKNOWN, GITHUB; - fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF) + fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIVEIRE, VICSCAN, ZCHAIN, ATLETA) } } } data class Explorer(val type: Type, val types: List, val url: String) { enum class Type { - POLKASCAN, SUBSCAN, ETHERSCAN, OKLINK, ZETA, REEF, UNKNOWN; + POLKASCAN, SUBSCAN, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, UNKNOWN; val capitalizedName: String get() = if (this == OKLINK) { From 6c530e596111362f181dcd06eac469e4174c5a26 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 12 Aug 2024 18:41:26 +0500 Subject: [PATCH 10/84] url format updates; rename --- .../impl/data/historySource/AtletaHistorySource.kt | 4 ++-- ...{FiveireHistorySource.kt => FireHistorySource.kt} | 8 ++++---- .../impl/data/historySource/HistorySourceProvider.kt | 4 ++-- .../impl/data/historySource/KlaytnHistorySource.kt | 12 ++++++------ .../impl/data/historySource/VicscanHistorySource.kt | 4 +++- ...chainHistorySource.kt => ZchainsHistorySource.kt} | 6 ++++-- ...eireHistoryResponse.kt => FireHistoryResponse.kt} | 10 +++++----- .../data/network/subquery/OperationsHistoryApi.kt | 6 +++--- .../soramitsu/runtime/multiNetwork/chain/Mappers.kt | 4 ++-- .../runtime/multiNetwork/chain/model/Chain.kt | 4 ++-- 10 files changed, 33 insertions(+), 29 deletions(-) rename feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/{FiveireHistorySource.kt => FireHistorySource.kt} (92%) rename feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/{ZchainHistorySource.kt => ZchainsHistorySource.kt} (93%) rename feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/{FiveireHistoryResponse.kt => FireHistoryResponse.kt} (82%) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt index 8d77943a6a..887bd92977 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/AtletaHistorySource.kt @@ -28,9 +28,9 @@ class AtletaHistorySource( val page = cursor?.toInt() ?: 1 val responseResult = runCatching { - val url = historyUrl.replace("{address}", accountAddress) + val urlBuilder = StringBuilder(historyUrl).append("addresses/").append(accountAddress).append("/transactions") walletOperationsApi.getAtletaOperationsHistory( - url = url + url = urlBuilder.toString() ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FireHistorySource.kt similarity index 92% rename from feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt rename to feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FireHistorySource.kt index 7f26b5239a..17b04cc7d5 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FiveireHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/FireHistorySource.kt @@ -12,7 +12,7 @@ import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter import jp.co.soramitsu.wallet.impl.domain.model.Operation -class FiveireHistorySource( +class FireHistorySource( private val walletOperationsApi: OperationsHistoryApi, private val historyUrl: String ) : HistorySource { @@ -28,9 +28,9 @@ class FiveireHistorySource( val page = cursor?.toInt() ?: 1 val responseResult = runCatching { - val url = historyUrl.replace("{address}", accountAddress) - walletOperationsApi.getFiveireOperationsHistory( - url = url, + val urlBuilder = StringBuilder(historyUrl).append("transactions/address/").append(accountAddress) + walletOperationsApi.getFireOperationsHistory( + url = urlBuilder.toString(), page = page, limit = pageSize ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt index 2823a04f65..063a73a5c1 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt @@ -23,9 +23,9 @@ class HistorySourceProvider( Chain.ExternalApi.Section.Type.ZETA -> ZetaHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.REEF -> ReefHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.KLAYTN -> KlaytnHistorySource(walletOperationsApi, historyUrl) - Chain.ExternalApi.Section.Type.FIVEIRE -> FiveireHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.FIRE -> FireHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.VICSCAN -> VicscanHistorySource(walletOperationsApi, historyUrl) - Chain.ExternalApi.Section.Type.ZCHAIN -> ZchainHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.ZCHAINS -> ZchainsHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.ATLETA -> AtletaHistorySource(walletOperationsApi, historyUrl) else -> null } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt index 3333668097..6880584438 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt @@ -1,9 +1,5 @@ package jp.co.soramitsu.wallet.impl.data.historySource -import android.os.Build -import java.text.SimpleDateFormat -import java.time.Instant -import java.util.Locale import jp.co.soramitsu.common.data.model.CursorPage import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain @@ -28,9 +24,13 @@ class KlaytnHistorySource( val page = cursor?.toInt() ?: 1 val responseResult = runCatching { - val url = historyUrl.replace("{address}", accountAddress) + val urlBuilder = StringBuilder(historyUrl) + .append("accounts") + .append(accountAddress) + .append("txs") + walletOperationsApi.getKlaytnOperationsHistory( - url = url, + url = urlBuilder.toString(), page = page ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt index 1f32f87872..ac772f5b6d 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/VicscanHistorySource.kt @@ -22,10 +22,12 @@ class VicscanHistorySource( accountAddress: String ): CursorPage { val page = cursor?.toInt() ?: 1 + val urlBuilder = StringBuilder(historyUrl).append("transaction/list") + val responseResult = runCatching { walletOperationsApi.getVicscanOperationsHistory( - url = historyUrl, + url = urlBuilder.toString(), account = accountAddress, limit = pageSize, offset = (page - 1) * pageSize diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainsHistorySource.kt similarity index 93% rename from feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt rename to feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainsHistorySource.kt index ceb1c7ff03..24484a34f5 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZchainsHistorySource.kt @@ -8,7 +8,7 @@ import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter import jp.co.soramitsu.wallet.impl.domain.model.Operation -class ZchainHistorySource( +class ZchainsHistorySource( private val walletOperationsApi: OperationsHistoryApi, private val historyUrl: String ) : HistorySource { @@ -22,10 +22,12 @@ class ZchainHistorySource( accountAddress: String ): CursorPage { val page = cursor?.toInt() ?: 1 + val urlBuilder = StringBuilder(historyUrl).append("transaction") + val responseResult = runCatching { walletOperationsApi.getZchainOperationsHistory( - url = historyUrl, + url = urlBuilder.toString(), address = accountAddress, pageSize = pageSize, page = page diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FireHistoryResponse.kt similarity index 82% rename from feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt rename to feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FireHistoryResponse.kt index 9ae3e03595..10877900a3 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FiveireHistoryResponse.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/FireHistoryResponse.kt @@ -4,19 +4,19 @@ import com.google.gson.annotations.SerializedName import java.math.BigInteger import jp.co.soramitsu.common.data.network.runtime.binding.BlockNumber -data class FiveireHistoryResponse( +data class FireHistoryResponse( val message: String, - val data: FiveireHistoryData, + val data: FireHistoryData, val page: Int, val total: Int ) -data class FiveireHistoryData( +data class FireHistoryData( val count: Int, - val transactions: List + val transactions: List ) -data class FiveireHistoryItem( +data class FireHistoryItem( val hash: String, @SerializedName("receipt_status") val status: Int, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt index c87c652ad7..9ae8483987 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt @@ -10,7 +10,7 @@ import jp.co.soramitsu.wallet.impl.data.network.model.request.SubqueryHistoryReq import jp.co.soramitsu.wallet.impl.data.network.model.request.SubsquidHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.response.AtletaHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.EtherscanHistoryResponse -import jp.co.soramitsu.wallet.impl.data.network.model.response.FiveireHistoryResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.FireHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.GiantsquidHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.KlaytnHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.OkLinkHistoryResponse @@ -85,11 +85,11 @@ interface OperationsHistoryApi { ): KlaytnHistoryResponse @GET - suspend fun getFiveireOperationsHistory( + suspend fun getFireOperationsHistory( @Url url: String, @Query("page") page: Int, @Query("limit") limit: Int - ): FiveireHistoryResponse + ): FireHistoryResponse @GET suspend fun getZchainOperationsHistory( diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt index 012d3c59e4..d71fb99059 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt @@ -35,9 +35,9 @@ private fun mapSectionTypeRemoteToSectionType(section: String) = when (section) "zeta" -> Chain.ExternalApi.Section.Type.ZETA "reef" -> Chain.ExternalApi.Section.Type.REEF "klaytn" -> Chain.ExternalApi.Section.Type.KLAYTN - "5ire" -> Chain.ExternalApi.Section.Type.FIVEIRE + "fire" -> Chain.ExternalApi.Section.Type.FIRE "vicscan" -> Chain.ExternalApi.Section.Type.VICSCAN - "zchain" -> Chain.ExternalApi.Section.Type.ZCHAIN + "zchain" -> Chain.ExternalApi.Section.Type.ZCHAINS "atleta" -> Chain.ExternalApi.Section.Type.ATLETA else -> Chain.ExternalApi.Section.Type.UNKNOWN } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt index ee733a2893..3a8a546d7b 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt @@ -67,9 +67,9 @@ data class Chain( ) { data class Section(val type: Type, val url: String) { enum class Type { - SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIVEIRE, VICSCAN, ZCHAIN, ATLETA, UNKNOWN, GITHUB; + SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS, ATLETA, UNKNOWN, GITHUB; - fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIVEIRE, VICSCAN, ZCHAIN, ATLETA) + fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS, ATLETA) } } } From 37f6151931bbcaf435c0ff8b591e796c9d38f7d9 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 13 Aug 2024 21:30:53 +0500 Subject: [PATCH 11/84] fixes, updates --- .../{ZetaHistorySource.kt => BlockscoutHistorySource.kt} | 6 +++--- .../wallet/impl/data/historySource/HistorySourceProvider.kt | 3 +-- .../wallet/impl/data/historySource/KlaytnHistorySource.kt | 4 ++-- .../jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt | 3 +-- .../co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) rename feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/{ZetaHistorySource.kt => BlockscoutHistorySource.kt} (94%) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZetaHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/BlockscoutHistorySource.kt similarity index 94% rename from feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZetaHistorySource.kt rename to feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/BlockscoutHistorySource.kt index abbca42e22..fd2a5ca6a2 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ZetaHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/BlockscoutHistorySource.kt @@ -15,7 +15,7 @@ import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter import jp.co.soramitsu.wallet.impl.domain.model.Operation import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount -class ZetaHistorySource( +class BlockscoutHistorySource( private val walletOperationsApi: OperationsHistoryApi, private val historyUrl: String ) : HistorySource { @@ -30,7 +30,7 @@ class ZetaHistorySource( ): CursorPage { val responseResult = runCatching { - val zetaUrl = StringBuilder(historyUrl).append(accountId.toHexString(true)).apply { + val urlBuilder = StringBuilder(historyUrl).append("addresses/").append(accountId.toHexString(true)).apply { when (chainAsset.ethereumType) { Asset.EthereumType.NORMAL -> { this.append("/transactions").toString() @@ -42,7 +42,7 @@ class ZetaHistorySource( } walletOperationsApi.getZetaOperationsHistory( - url = zetaUrl.toString() + url = urlBuilder.toString() ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt index 063a73a5c1..fa5bcc55d9 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt @@ -20,13 +20,12 @@ class HistorySourceProvider( Chain.ExternalApi.Section.Type.GIANTSQUID -> GiantsquidHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.ETHERSCAN -> EtherscanHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.OKLINK -> OkLinkHistorySource(walletOperationsApi, historyUrl) - Chain.ExternalApi.Section.Type.ZETA -> ZetaHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.BLOCKSCOUT -> BlockscoutHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.REEF -> ReefHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.KLAYTN -> KlaytnHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.FIRE -> FireHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.VICSCAN -> VicscanHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.ZCHAINS -> ZchainsHistorySource(walletOperationsApi, historyUrl) - Chain.ExternalApi.Section.Type.ATLETA -> AtletaHistorySource(walletOperationsApi, historyUrl) else -> null } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt index 6880584438..43867990ac 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/KlaytnHistorySource.kt @@ -25,9 +25,9 @@ class KlaytnHistorySource( val responseResult = runCatching { val urlBuilder = StringBuilder(historyUrl) - .append("accounts") + .append("accounts/") .append(accountAddress) - .append("txs") + .append("/txs") walletOperationsApi.getKlaytnOperationsHistory( url = urlBuilder.toString(), diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt index d71fb99059..4e0c60f94c 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt @@ -32,13 +32,12 @@ private fun mapSectionTypeRemoteToSectionType(section: String) = when (section) "sora" -> Chain.ExternalApi.Section.Type.SORA "etherscan" -> Chain.ExternalApi.Section.Type.ETHERSCAN "oklink" -> Chain.ExternalApi.Section.Type.OKLINK - "zeta" -> Chain.ExternalApi.Section.Type.ZETA + "blockscout" -> Chain.ExternalApi.Section.Type.BLOCKSCOUT "reef" -> Chain.ExternalApi.Section.Type.REEF "klaytn" -> Chain.ExternalApi.Section.Type.KLAYTN "fire" -> Chain.ExternalApi.Section.Type.FIRE "vicscan" -> Chain.ExternalApi.Section.Type.VICSCAN "zchain" -> Chain.ExternalApi.Section.Type.ZCHAINS - "atleta" -> Chain.ExternalApi.Section.Type.ATLETA else -> Chain.ExternalApi.Section.Type.UNKNOWN } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt index 3a8a546d7b..2943d98167 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt @@ -67,9 +67,9 @@ data class Chain( ) { data class Section(val type: Type, val url: String) { enum class Type { - SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS, ATLETA, UNKNOWN, GITHUB; + SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, BLOCKSCOUT, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS, UNKNOWN, GITHUB; - fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS, ATLETA) + fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, BLOCKSCOUT, REEF, KLAYTN, FIRE, VICSCAN, ZCHAINS) } } } From 09612ae1fae40d395b1694eec210735a13dbddfe Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 14 Aug 2024 11:12:12 +0500 Subject: [PATCH 12/84] update chain urls --- runtime/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/build.gradle b/runtime/build.gradle index 4ce13e2b8c..32dd1f23d9 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -18,11 +18,11 @@ android { buildTypes { debug { - buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/develop-free/chains/v10/chains_dev.json\"" + buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/develop-free/chains/v11/chains_dev.json\"" } release { - buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/v10/chains.json\"" + buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/v11/chains.json\"" } } From 066ae8b16a38fbc709431501795770160a4d42c3 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 14 Aug 2024 15:33:34 +0500 Subject: [PATCH 13/84] balance update improve --- .../blockchain/updaters/BalanceUpdateTrigger.kt | 14 +++++++++++--- .../balance/detail/BalanceDetailViewModel.kt | 4 ++++ .../balance/list/BalanceListViewModel.kt | 10 +++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalanceUpdateTrigger.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalanceUpdateTrigger.kt index 9b0cf241e0..11e1091d29 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalanceUpdateTrigger.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalanceUpdateTrigger.kt @@ -5,9 +5,11 @@ import jp.co.soramitsu.core.models.ChainId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +const val UPDATE_TIMEOUT = 30_000L + object BalanceUpdateTrigger { - private var lastUpdateTime: Long? = null + private var lastUpdateTimeMap = mutableMapOf() private val flow = MutableSharedFlow() @@ -17,8 +19,14 @@ object BalanceUpdateTrigger { suspend operator fun invoke(chainId: ChainId? = null, force: Boolean = false) { val currentTime = getTimeMillis() - if (force.not() && lastUpdateTime != null && currentTime - lastUpdateTime!! <= 30_000L) return + val chainLastUpdateTime = chainId?.let { lastUpdateTimeMap[chainId] } + + if (force.not() && chainLastUpdateTime != null && currentTime - chainLastUpdateTime <= UPDATE_TIMEOUT) { + return + } flow.emit(chainId) - lastUpdateTime = getTimeMillis() + chainId?.let { + lastUpdateTimeMap[chainId] = getTimeMillis() + } } } \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt index 14d1b41194..320e427cab 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt @@ -204,6 +204,10 @@ class BalanceDetailViewModel @Inject constructor( chainId?.let { selectedChainId.value = chainId } }.launchIn(viewModelScope) + selectedChainId.onEach { chainId -> + BalanceUpdateTrigger.invoke(chainId = chainId) + }.launchIn(viewModelScope) + transactionHistoryProvider.sideEffects().onEach { when (it) { is TransactionHistoryUi.SideEffect.Error -> showError( diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index b6eb81c516..a58e709a30 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -617,6 +617,10 @@ class BalanceListViewModel @Inject constructor( selectedChainId.value = chainId }.launchIn(this) + selectedChainId.onEach { chainId -> + BalanceUpdateTrigger.invoke(chainId = chainId) + }.launchIn(this) + interactor.selectedLightMetaAccountFlow().map { wallet -> if (pendulumPreInstalledAccountsScenario.isPendulumMode(wallet.id)) { selectedChainId.value = pendulumChainId @@ -631,7 +635,7 @@ class BalanceListViewModel @Inject constructor( } override fun onRefresh() { - refresh() + sync() viewModelScope.launch { BalanceUpdateTrigger.invoke() } @@ -653,10 +657,6 @@ class BalanceListViewModel @Inject constructor( } } - private fun refresh() { - sync() - } - fun onResume() { viewModelScope.launch { interactor.selectedMetaAccountFlow().collect { From b3470c3a9020c19004f6aa0593ff04fd81ae8f7a Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 16 Aug 2024 11:16:23 +0700 Subject: [PATCH 14/84] make ethereum balance work faster Signed-off-by: Deneath --- .../account/impl/domain/WalletSyncService.kt | 176 ++++++++++-------- .../updaters/BalancesUpdateSystem.kt | 53 ++++-- 2 files changed, 138 insertions(+), 91 deletions(-) diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt index 3418e8d7d1..a0c67e2320 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/WalletSyncService.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter @@ -53,6 +54,7 @@ import kotlinx.coroutines.withTimeoutOrNull import java.math.BigInteger private const val TAG = "WalletSyncService" + class WalletSyncService( private val metaAccountDao: MetaAccountDao, private val chainsRepository: ChainsRepository, @@ -117,63 +119,69 @@ class WalletSyncService( launch { ethereumChains.forEach { chain -> launch { - val assetsDeferred = async { - if (chainRegistry.checkChainSyncedUp(chain).not()) { - chainRegistry.setupChain(chain) + if (chainRegistry.checkChainSyncedUp(chain).not()) { + chainRegistry.setupChain(chain) + } + val connection = + withTimeoutOrNull(CHAIN_SYNC_TIMEOUT_MILLIS) { + val connection = + chainRegistry.awaitEthereumConnection(chain.id) + // await connecting to the node + connection.statusFlow.first { it is EvmConnectionStatus.Connected } + connection } - val connection = - withTimeoutOrNull(CHAIN_SYNC_TIMEOUT_MILLIS) { - val connection = - chainRegistry.awaitEthereumConnection(chain.id) - // await connecting to the node - connection.statusFlow.first { it is EvmConnectionStatus.Connected } - connection - } - + val assetsDeferred = async { metaAccounts.mapNotNull { metaAccount -> val accountId = metaAccount.accountId(chain) ?: return@mapNotNull null - chain.assets.map { chainAsset -> - val balance = kotlin.runCatching { - connection?.web3j?.fetchEthBalance( - chainAsset, - accountId.toHexString(true) - ) - }.getOrNull() + val accountBalancesDeferred = + chain.assets.map { chainAsset -> + async { + val balance = kotlin.runCatching { + connection?.web3j?.fetchEthBalance( + chainAsset, + accountId.toHexString(true) + ) + }.getOrNull() - if (balance.positiveOrNull() != null) { - accountHasAssetWithPositiveBalanceMap[metaAccount.id] = - true - } + if (balance.positiveOrNull() != null) { + accountHasAssetWithPositiveBalanceMap[metaAccount.id] = + true + } - val isPopularUtilityAsset = - chain.rank != null && chainAsset.isUtility - val accountHasAssetWithPositiveBalance = - accountHasAssetWithPositiveBalanceMap[metaAccount.id] == true + val isPopularUtilityAsset = + chain.rank != null && chainAsset.isUtility + val accountHasAssetWithPositiveBalance = + accountHasAssetWithPositiveBalanceMap[metaAccount.id] == true - AssetLocal( - id = chainAsset.id, - chainId = chain.id, - accountId = accountId, - metaId = metaAccount.id, - tokenPriceId = chainAsset.priceId, - freeInPlanks = balance, - reservedInPlanks = BigInteger.ZERO, - miscFrozenInPlanks = BigInteger.ZERO, - feeFrozenInPlanks = BigInteger.ZERO, - bondedInPlanks = BigInteger.ZERO, - redeemableInPlanks = BigInteger.ZERO, - unbondingInPlanks = BigInteger.ZERO, - enabled = balance.positiveOrNull() != null || (!accountHasAssetWithPositiveBalance && isPopularUtilityAsset) - ) - } + AssetLocal( + id = chainAsset.id, + chainId = chain.id, + accountId = accountId, + metaId = metaAccount.id, + tokenPriceId = chainAsset.priceId, + freeInPlanks = balance, + reservedInPlanks = BigInteger.ZERO, + miscFrozenInPlanks = BigInteger.ZERO, + feeFrozenInPlanks = BigInteger.ZERO, + bondedInPlanks = BigInteger.ZERO, + redeemableInPlanks = BigInteger.ZERO, + unbondingInPlanks = BigInteger.ZERO, + enabled = balance.positiveOrNull() != null || (!accountHasAssetWithPositiveBalance && isPopularUtilityAsset) + ) + } + } + + accountBalancesDeferred.awaitAll() }.flatten() } val localAssets = assetsDeferred.await() assetDao.insertAssets(localAssets) - hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts(metaAccounts) + hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts( + metaAccounts + ) } } } @@ -195,7 +203,11 @@ class WalletSyncService( chain.utilityAsset != null && chain.utilityAsset!!.typeExtra == ChainAssetType.Equilibrium if (isEquilibriumTypeChain) { - buildEquilibriumAssetsByMetaAccounts(metaAccounts, chain, runtime) + buildEquilibriumAssetsByMetaAccounts( + metaAccounts, + chain, + runtime + ) } else { val allAccountsStorageKeys = metaAccounts.mapNotNull { metaAccount -> @@ -281,7 +293,9 @@ class WalletSyncService( } val localAssets = assetsDeferred.await() assetDao.insertAssets(localAssets) - hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts(metaAccounts) + hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts( + metaAccounts + ) } } } @@ -291,7 +305,9 @@ class WalletSyncService( coroutineScope { metaAccountDao.markAccountsInitialized(metaAccounts.map { it.id }) - hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts(metaAccounts) + hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts( + metaAccounts + ) } } } @@ -302,7 +318,8 @@ class WalletSyncService( metaAccountDao.observeNotInitializedChainAccounts().filter { it.isNotEmpty() } .onEach { chainAccounts -> chainRegistry.configsSyncDeferred.joinAll() - val chains = chainAccounts.map { chainRegistry.getChain(it.chainId) }.associateBy { it.id } + val chains = + chainAccounts.map { chainRegistry.getChain(it.chainId) }.associateBy { it.id } val ethereumChains = chains.values.filter { it.isEthereumChain }.sortedByDescending { it.rank } val substrateChains = @@ -329,30 +346,33 @@ class WalletSyncService( chainAccount.accountId val accountId = chainAccount.accountId - chain.assets.map { chainAsset -> - val balance = kotlin.runCatching { - connection?.web3j?.fetchEthBalance( - chainAsset, - accountId.toHexString(true) - ) - }.getOrNull() + val accountBalances = chain.assets.map { chainAsset -> + async { + val balance = kotlin.runCatching { + connection?.web3j?.fetchEthBalance( + chainAsset, + accountId.toHexString(true) + ) + }.getOrNull() - AssetLocal( - id = chainAsset.id, - chainId = chain.id, - accountId = accountId, - metaId = chainAccount.metaId, - tokenPriceId = chainAsset.priceId, - freeInPlanks = balance, - reservedInPlanks = BigInteger.ZERO, - miscFrozenInPlanks = BigInteger.ZERO, - feeFrozenInPlanks = BigInteger.ZERO, - bondedInPlanks = BigInteger.ZERO, - redeemableInPlanks = BigInteger.ZERO, - unbondingInPlanks = BigInteger.ZERO, - enabled = balance.positiveOrNull()!= null || chainAsset.isUtility - ) + AssetLocal( + id = chainAsset.id, + chainId = chain.id, + accountId = accountId, + metaId = chainAccount.metaId, + tokenPriceId = chainAsset.priceId, + freeInPlanks = balance, + reservedInPlanks = BigInteger.ZERO, + miscFrozenInPlanks = BigInteger.ZERO, + feeFrozenInPlanks = BigInteger.ZERO, + bondedInPlanks = BigInteger.ZERO, + redeemableInPlanks = BigInteger.ZERO, + unbondingInPlanks = BigInteger.ZERO, + enabled = balance.positiveOrNull() != null || chainAsset.isUtility + ) + } } + accountBalances.awaitAll() }.flatten() } val localAssets = assetsDeferred.await() @@ -378,7 +398,11 @@ class WalletSyncService( chain.utilityAsset != null && chain.utilityAsset!!.typeExtra == ChainAssetType.Equilibrium if (isEquilibriumTypeChain) { - buildEquilibriumAssets(chainAccounts.map { it.metaId to it.accountId }, chain, runtime) + buildEquilibriumAssets( + chainAccounts.map { it.metaId to it.accountId }, + chain, + runtime + ) } else { val allAccountsStorageKeys = chainAccounts.map { chainAccount -> @@ -466,7 +490,9 @@ class WalletSyncService( .launchIn(scope) } - private suspend fun hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts(metaAccounts: List) { + private suspend fun hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaAccounts( + metaAccounts: List + ) { hideEmptyAssetsIfThereAreAtLeastOnePositiveBalanceByMetaIds(metaAccounts.map { it.id }) } @@ -487,7 +513,11 @@ class WalletSyncService( chain: Chain, runtime: RuntimeSnapshot? ): List { - return buildEquilibriumAssets(metaAccounts.map { it.id to it.accountId(chain) }, chain, runtime) + return buildEquilibriumAssets( + metaAccounts.map { it.id to it.accountId(chain) }, + chain, + runtime + ) } private suspend fun buildEquilibriumAssets( diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt index d01d3e322b..c7429cbcae 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt @@ -41,6 +41,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow @@ -218,27 +220,42 @@ class BalancesUpdateSystem( chain: Chain, accounts: List ) { - accounts.forEach { account -> - val address = account.address(chain) ?: return@forEach - val accountId = account.accountId(chain) ?: return@forEach - chain.assets.forEach asset@{ asset -> - val balance = - kotlin.runCatching { ethereumRemoteSource.fetchEthBalance(asset, address) } - .onFailure { - Log.d( - TAG, - "fetchEthBalance error ${it.message} ${it.localizedMessage} $it" + coroutineScope { + + val accountsDeferred = accounts.map { account -> + async { + val address = account.address(chain) ?: return@async + val accountId = account.accountId(chain) ?: return@async + val accountBalancesDeferred = chain.assets.map { asset -> + async assets@{ + val balance = + kotlin.runCatching { + ethereumRemoteSource.fetchEthBalance( + asset, + address + ) + } + .onFailure { + Log.d( + TAG, + "fetchEthBalance error ${it.message} ${it.localizedMessage} $it" + ) + } + .getOrNull() ?: return@assets + val balanceData = SimpleBalanceData(balance) + assetCache.updateAsset( + metaId = account.id, + accountId = accountId, + asset = asset, + balanceData = balanceData ) } - .getOrNull() ?: return@asset - val balanceData = SimpleBalanceData(balance) - assetCache.updateAsset( - metaId = account.id, - accountId = accountId, - asset = asset, - balanceData = balanceData - ) + } + accountBalancesDeferred.awaitAll() + } } + + accountsDeferred.awaitAll() } } From 4149c4a24f93c4f1fed6837560d974f7fb4ba4aa Mon Sep 17 00:00:00 2001 From: Deneath Date: Tue, 20 Aug 2024 11:30:55 +0700 Subject: [PATCH 15/84] FLW-4687 possible fix of start staking pool data loading Signed-off-by: Deneath --- .../setup/pool/StartStakingPoolViewModel.kt | 187 +++++++++--------- 1 file changed, 95 insertions(+), 92 deletions(-) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/StartStakingPoolViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/StartStakingPoolViewModel.kt index 28dbf59591..b15d226287 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/StartStakingPoolViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/StartStakingPoolViewModel.kt @@ -2,17 +2,12 @@ package jp.co.soramitsu.staking.impl.presentation.setup.pool import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.common.AlertViewState import jp.co.soramitsu.common.BuildConfig import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.ToolbarViewState import jp.co.soramitsu.common.resources.ResourceManager -import jp.co.soramitsu.common.utils.flowOf -import jp.co.soramitsu.common.utils.inBackground import jp.co.soramitsu.feature_staking_impl.R -import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.polkadotChainId import jp.co.soramitsu.staking.api.data.StakingSharedState import jp.co.soramitsu.staking.impl.domain.rewards.RewardCalculatorFactory @@ -25,12 +20,13 @@ import jp.co.soramitsu.staking.impl.presentation.staking.main.scenarios.PERIOD_Y import jp.co.soramitsu.staking.impl.scenarios.StakingPoolInteractor import jp.co.soramitsu.staking.impl.scenarios.relaychain.HOURS_IN_DAY import jp.co.soramitsu.staking.impl.scenarios.relaychain.StakingRelayChainScenarioInteractor -import jp.co.soramitsu.wallet.impl.domain.model.Asset -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject @HiltViewModel class StartStakingPoolViewModel @Inject constructor( @@ -43,84 +39,93 @@ class StartStakingPoolViewModel @Inject constructor( private val flowStateProvider: StakingPoolSharedStateProvider ) : BaseViewModel() { - val chain: Chain - val asset: Asset + private val assetDeferred = viewModelScope.async { stakingSharedState.currentAssetFlow().first() } + private val chainDeferred = viewModelScope.async { stakingSharedState.assetWithChain.first().chain } init { - val mainState = requireNotNull(flowStateProvider.mainState.get()) - chain = requireNotNull(mainState.chain) - asset = requireNotNull(mainState.asset) - } - - private val poolsLimitHasReached = flowOf { - val possiblePools = stakingPoolInteractor.getPossiblePools(chain.id) - val poolsCount = stakingPoolInteractor.getPoolsCount(chain.id) - return@flowOf poolsCount >= possiblePools - }.inBackground().stateIn(viewModelScope, SharingStarted.Eagerly, null) - - private val yearlyReturnsFlow = flowOf { - val asset = stakingSharedState.currentAssetFlow().first() - // todo hardcoded returns for demo - val kusamaOnTestNodeChainId = "51cdb4b3101904a9d234d126656d33cd17518249819b510a03d6c90d0a019611" - val polkadotOnTestNodeChainId = "4f77f65b21b1f396c1555850be6f21e2b1f36c26b94dbcbfec976901c9f08bf3" - val chainId = if (asset.token.configuration.chainId == kusamaOnTestNodeChainId || asset.token.configuration.chainId == polkadotOnTestNodeChainId) { - polkadotChainId - } else { - asset.token.configuration.chainId - } - val rewardCalculator = rewardCalculatorFactory.create(asset.token.configuration) - val yearly = rewardCalculator.calculateReturns(BigDecimal.ONE, PERIOD_YEAR, true, chainId) - - mapPeriodReturnsToRewardEstimation(yearly, asset.token, resourceManager) - } + viewModelScope.launch { + val rewardsPayoutDelayDeferred = async { + val hours = relayChainScenarioInteractor.stakePeriodInHours() + resourceManager.getQuantityString(R.plurals.common_hours_format, hours, hours) + } + val unstakingPeriodDeferred = async { + val lockupPeriodInHours = relayChainScenarioInteractor.unstakingPeriod() + if (lockupPeriodInHours > HOURS_IN_DAY) { + val inDays = lockupPeriodInHours / HOURS_IN_DAY + resourceManager.getQuantityString(R.plurals.common_days_format, inDays, inDays) + } else { + resourceManager.getQuantityString( + R.plurals.common_hours_format, + lockupPeriodInHours, + lockupPeriodInHours + ) + } + } + val asset = assetDeferred.await() + val chain = chainDeferred.await() + val yearlyReturnsDeferred = async { + // todo hardcoded returns for demo + val kusamaOnTestNodeChainId = + "51cdb4b3101904a9d234d126656d33cd17518249819b510a03d6c90d0a019611" + val polkadotOnTestNodeChainId = + "4f77f65b21b1f396c1555850be6f21e2b1f36c26b94dbcbfec976901c9f08bf3" + val chainId = + if (chain.id == kusamaOnTestNodeChainId || chain.id == polkadotOnTestNodeChainId) { + polkadotChainId + } else { + chain.id + } + val rewardCalculator = rewardCalculatorFactory.create(asset.token.configuration) + val yearly = + rewardCalculator.calculateReturns(BigDecimal.ONE, PERIOD_YEAR, true, chainId) - private val unstakingPeriodFlow = flowOf { - val lockupPeriodInHours = relayChainScenarioInteractor.unstakingPeriod() - if (lockupPeriodInHours > HOURS_IN_DAY) { - val inDays = lockupPeriodInHours / HOURS_IN_DAY - resourceManager.getQuantityString(R.plurals.common_days_format, inDays, inDays) - } else { - resourceManager.getQuantityString(R.plurals.common_hours_format, lockupPeriodInHours, lockupPeriodInHours) + mapPeriodReturnsToRewardEstimation( + yearly, + asset.token, + resourceManager + ) + } + state.update { prevState -> + prevState.copy( + assetName = asset.token.configuration.symbol, + rewardsPayoutDelay = rewardsPayoutDelayDeferred.await(), + unstakingPeriod = unstakingPeriodDeferred.await(), + yearlyEstimatedEarnings = yearlyReturnsDeferred.await().gain + ) + } } } - private val rewardsPayoutDelayFlow = flowOf { - val hours = relayChainScenarioInteractor.stakePeriodInHours() - resourceManager.getQuantityString(R.plurals.common_hours_format, hours, hours) + private val poolsLimitHasReachedDeferred = viewModelScope.async { + val chain = chainDeferred.await() + val possiblePools = stakingPoolInteractor.getPossiblePools(chain.id) + val poolsCount = stakingPoolInteractor.getPoolsCount(chain.id) + poolsCount >= possiblePools } - val state = combine(rewardsPayoutDelayFlow, unstakingPeriodFlow, yearlyReturnsFlow) { rewardsPayoutDelay, unstakingPeriod, yearlyReturns -> - SetupStakingPoolViewState( - ToolbarViewState( - resourceManager.getString(R.string.pool_staking_title), - R.drawable.ic_arrow_back_24dp - ), - asset.token.configuration.symbol, - rewardsPayoutDelay, - yearlyReturns.gain, - unstakingPeriod - ) - }.stateIn( - this.viewModelScope, - SharingStarted.Eagerly, - SetupStakingPoolViewState( - ToolbarViewState( - resourceManager.getString(R.string.pool_staking_title), - R.drawable.ic_arrow_back_24dp - ), - "...", - "...", - "...", - "..." + val state: MutableStateFlow = + MutableStateFlow( + SetupStakingPoolViewState( + ToolbarViewState( + resourceManager.getString(R.string.pool_staking_title), + R.drawable.ic_arrow_back_24dp + ), + "...", + "...", + "...", + "..." + ) ) - ) fun onBackClick() { router.back() } fun onInstructionsClick() { - router.openWebViewer(resourceManager.getString(R.string.pool_staking_title), BuildConfig.STAKING_POOL_WIKI) + router.openWebViewer( + resourceManager.getString(R.string.pool_staking_title), + BuildConfig.STAKING_POOL_WIKI + ) } fun onJoinPool() { @@ -132,26 +137,24 @@ class StartStakingPoolViewModel @Inject constructor( } fun onCreatePool() { - val limitHasReached = poolsLimitHasReached.value - if (limitHasReached == null) { - showError("Error poolsLimitHasReached value not provided") - return - } - if (limitHasReached) { - router.openAlert( - AlertViewState( - title = resourceManager.getString(R.string.pools_limit_has_reached_error_title), - message = resourceManager.getString(R.string.pools_limit_has_reached_error_message), - buttonText = resourceManager.getString(R.string.common_got_it), - iconRes = R.drawable.ic_status_warning_16 + viewModelScope.launch { + val limitHasReached = poolsLimitHasReachedDeferred.await() + if (limitHasReached) { + router.openAlert( + AlertViewState( + title = resourceManager.getString(R.string.pools_limit_has_reached_error_title), + message = resourceManager.getString(R.string.pools_limit_has_reached_error_message), + buttonText = resourceManager.getString(R.string.common_got_it), + iconRes = R.drawable.ic_status_warning_16 + ) ) - ) - return - } - val setupState = flowStateProvider.createFlowState - if (setupState.get() == null) { - setupState.set(StakingPoolCreateFlowState()) + return@launch + } + val setupState = flowStateProvider.createFlowState + if (setupState.get() == null) { + setupState.set(StakingPoolCreateFlowState()) + } + router.openCreatePoolSetup() } - router.openCreatePoolSetup() } } From 3d4b15cafd38425b1d31757c94c2fe784c7406b9 Mon Sep 17 00:00:00 2001 From: Deneath Date: Tue, 20 Aug 2024 11:49:31 +0700 Subject: [PATCH 16/84] FLW-4666 hide coingecko error Signed-off-by: Deneath --- .../presentation/balance/list/BalanceListViewModel.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index a58e709a30..ef4998da9e 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -10,10 +10,6 @@ import androidx.lifecycle.viewModelScope import co.jp.soramitsu.walletconnect.domain.WalletConnectInteractor import com.walletconnect.android.internal.common.exception.MalformedWalletConnectUri import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import java.math.BigInteger -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.PendulumPreInstalledAccountsScenario import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor @@ -118,6 +114,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.math.BigDecimal +import java.math.BigInteger +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject private const val CURRENT_ICON_SIZE = 40 @@ -697,7 +697,6 @@ class BalanceListViewModel @Inject constructor( interactor.syncAssetsRates().onFailure { withContext(Dispatchers.Main) { selectedFiat.notifySyncFailed() - showError(it) } } } From b1ae1adda391a1c6fc0e81355a656e4c505576c8 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 14 Jun 2024 11:53:08 +0300 Subject: [PATCH 17/84] fix tests --- core-db/build.gradle | 4 +++- .../runtime/multiNetwork/chain/ChainSyncServiceTest.kt | 10 +++++++++- .../multiNetwork/runtime/RuntimeProviderTest.kt | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core-db/build.gradle b/core-db/build.gradle index 985c15b7be..de509b4b32 100644 --- a/core-db/build.gradle +++ b/core-db/build.gradle @@ -59,5 +59,7 @@ dependencies { androidTestImplementation libs.ext.junit androidTestImplementation libs.room.testing - androidTestImplementation project(':test-shared') + androidTestImplementation projects.testShared + testImplementation libs.junit.junit + testImplementation projects.testShared } \ No newline at end of file diff --git a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt index 0fdbd7427e..125b1e0e81 100644 --- a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt +++ b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt @@ -1,6 +1,8 @@ package jp.co.soramitsu.runtime.multiNetwork.chain +import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao +import jp.co.soramitsu.coredb.dao.MetaAccountDao import jp.co.soramitsu.coredb.model.chain.ChainLocal import jp.co.soramitsu.coredb.model.chain.JoinedChainInfo import jp.co.soramitsu.runtime.multiNetwork.chain.remote.ChainFetcher @@ -65,6 +67,12 @@ class ChainSyncServiceTest { @Mock lateinit var dao: ChainDao + @Mock + lateinit var metaAccountDao: MetaAccountDao + + @Mock + lateinit var assetsDao: AssetDao + @Mock lateinit var chainFetcher: ChainFetcher @@ -72,7 +80,7 @@ class ChainSyncServiceTest { @Before fun setup() { - chainSyncService = ChainSyncService(dao, chainFetcher) + chainSyncService = ChainSyncService(dao, chainFetcher, metaAccountDao, assetsDao) } @Test diff --git a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProviderTest.kt b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProviderTest.kt index b7024d4789..612aa153cf 100644 --- a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProviderTest.kt +++ b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProviderTest.kt @@ -1,6 +1,6 @@ package jp.co.soramitsu.runtime.multiNetwork.runtime -import jp.co.soramitsu.common.mixin.api.networkStateService +import jp.co.soramitsu.common.domain.NetworkStateService import jp.co.soramitsu.core.runtime.ConstructedRuntime import jp.co.soramitsu.core.runtime.RuntimeFactory import jp.co.soramitsu.coredb.dao.ChainDao @@ -50,7 +50,7 @@ class RuntimeProviderTest { lateinit var chainDao: ChainDao @Mock - lateinit var networkStateService: networkStateService + lateinit var networkStateService: NetworkStateService lateinit var runtimeProvider: RuntimeProvider From ea3d8e8277656ac3b6065ea969f327340edb1b64 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 14 Jun 2024 11:54:09 +0300 Subject: [PATCH 18/84] libs update; minSdk 24->26 --- gradle/libs.versions.toml | 4 +++- .../jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd1b2fe9a4..5bd03bef13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanistVersion = "0.34.0" activityCompose = "1.8.2" -android_plugin = "8.6.0-rc01" +android_plugin = "8.5.0" appcompat = "1.6.1" architectureComponentVersion = "2.7.0" beaconVersion = "3.2.4" @@ -60,6 +60,7 @@ wsVersion = "2.14" xNetworking = "0.2.5-temp7" zxingEmbeddedVersion = "4.3.0" zxingVersion = "3.5.3" +junitJunit = "4.12" [libraries] appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -176,6 +177,7 @@ web3jDep = { module = "org.web3j:core", version.ref = "web3j" } walletconnectBomDep = { module = "com.walletconnect:android-bom", version.ref = "walletconnectBom" } walletconnectCoreDep = { module = "com.walletconnect:android-core" } walletconnectWeb3WalletDep = { module = "com.walletconnect:web3wallet" } +junit-junit = { group = "junit", name = "junit", version.ref = "junitJunit" } [bundles] compose = [ diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt index dc4beaa127..3fdf02bad6 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt @@ -280,7 +280,7 @@ class ChainRegistry @Inject constructor( override fun getConnection(chainId: String) = connectionPool.getConnectionOrThrow(chainId) - suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) + override suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) @Deprecated( "Since we have ethereum chains, which don't have runtime, we must use the function with nullable return value", ReplaceWith("getRuntimeOrNull(chainId)") From 4c1ea22522e7055c8cfc4a5cfeb8f8a2c0f1e22f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 14 Jun 2024 15:45:17 +0300 Subject: [PATCH 19/84] rid of duplicate bouncycastle deps --- app/build.gradle | 4 ---- build.gradle | 11 ++++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 83ef89f8bc..19b614d190 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -129,10 +129,6 @@ android { resources.excludes.add("META-INF/**/*") resources.excludes.add("META-INF/*") } - - configurations.configureEach { - exclude group: "org.bouncycastle", module: "bcprov-jdk15on" - } } play { diff --git a/build.gradle b/build.gradle index 9d0b5f386c..ae9604dcd6 100644 --- a/build.gradle +++ b/build.gradle @@ -61,10 +61,19 @@ allprojects { } } } + + configurations.configureEach { + resolutionStrategy { + // add dependency substitution rules + dependencySubstitution { + substitute module('org.bouncycastle:bcprov-jdk15on') using module('org.bouncycastle:bcprov-jdk18on:1.78') + } + } + } } tasks.register('clean', Delete) { - delete rootProject.buildDir + delete rootProject.layout.buildDirectory } tasks.register('runTest', GradleBuild) { From 4492a7c572e4d6131354965717939b7da3b133b6 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 14 Jun 2024 15:59:09 +0300 Subject: [PATCH 20/84] fix duplicate res --- feature-account-api/build.gradle | 3 +++ feature-account-impl/build.gradle | 3 +++ feature-crowdloan-impl/build.gradle | 3 +++ feature-nft-api/build.gradle.kts | 2 ++ feature-nft-impl/build.gradle.kts | 2 ++ feature-onboarding-api/build.gradle | 3 +++ feature-onboarding-impl/build.gradle | 3 +++ feature-polkaswap-api/build.gradle.kts | 2 ++ feature-polkaswap-impl/build.gradle.kts | 3 +++ feature-soracard-api/build.gradle.kts | 2 ++ feature-soracard-impl/build.gradle.kts | 3 +++ feature-splash/build.gradle | 3 +++ feature-staking-api/build.gradle | 3 +++ feature-staking-impl/build.gradle | 3 +++ feature-success-impl/build.gradle.kts | 3 +++ feature-wallet-api/build.gradle | 3 +++ feature-wallet-impl/build.gradle | 3 +++ feature-walletconnect-api/build.gradle.kts | 2 ++ feature-walletconnect-impl/build.gradle.kts | 2 ++ 19 files changed, 51 insertions(+) diff --git a/feature-account-api/build.gradle b/feature-account-api/build.gradle index 23b4d2491b..a6f48055f0 100644 --- a/feature-account-api/build.gradle +++ b/feature-account-api/build.gradle @@ -27,6 +27,9 @@ android { jvmTarget = '17' } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_account_api' } diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 6ea2148632..2c994d1344 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -34,6 +34,9 @@ android { composeOptions { kotlinCompilerExtensionVersion composeCompilerVersion } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_account_impl' } diff --git a/feature-crowdloan-impl/build.gradle b/feature-crowdloan-impl/build.gradle index b906a1af53..72a3bb441c 100644 --- a/feature-crowdloan-impl/build.gradle +++ b/feature-crowdloan-impl/build.gradle @@ -29,6 +29,9 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_crowdloan_impl' } diff --git a/feature-nft-api/build.gradle.kts b/feature-nft-api/build.gradle.kts index 36d9ad3a6c..905789ff5a 100644 --- a/feature-nft-api/build.gradle.kts +++ b/feature-nft-api/build.gradle.kts @@ -21,6 +21,8 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-nft-impl/build.gradle.kts b/feature-nft-impl/build.gradle.kts index 6e6bd6c84a..bf735dd990 100644 --- a/feature-nft-impl/build.gradle.kts +++ b/feature-nft-impl/build.gradle.kts @@ -41,6 +41,8 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-onboarding-api/build.gradle b/feature-onboarding-api/build.gradle index 36d098486e..b800e35472 100644 --- a/feature-onboarding-api/build.gradle +++ b/feature-onboarding-api/build.gradle @@ -22,6 +22,9 @@ android { jvmTarget = '17' } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_onboarding_api' } diff --git a/feature-onboarding-impl/build.gradle b/feature-onboarding-impl/build.gradle index b4d6ce7984..4941c37862 100644 --- a/feature-onboarding-impl/build.gradle +++ b/feature-onboarding-impl/build.gradle @@ -45,6 +45,9 @@ android { composeOptions { kotlinCompilerExtensionVersion composeCompilerVersion } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_onboarding_impl' } diff --git a/feature-polkaswap-api/build.gradle.kts b/feature-polkaswap-api/build.gradle.kts index 0fd6ae5bfe..5f0b01af70 100644 --- a/feature-polkaswap-api/build.gradle.kts +++ b/feature-polkaswap-api/build.gradle.kts @@ -21,6 +21,8 @@ android { jvmTarget = "17" } + packaging { resources.excludes.add("META-INF/*") } + namespace = "jp.co.soramitsu.feature_polkaswap_api" } diff --git a/feature-polkaswap-impl/build.gradle.kts b/feature-polkaswap-impl/build.gradle.kts index 9814fbb69b..5eb6babd8e 100644 --- a/feature-polkaswap-impl/build.gradle.kts +++ b/feature-polkaswap-impl/build.gradle.kts @@ -30,6 +30,9 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } + namespace = "jp.co.soramitsu.feature_polkaswap_impl" } diff --git a/feature-soracard-api/build.gradle.kts b/feature-soracard-api/build.gradle.kts index ada3c686d6..54e9aa4d39 100644 --- a/feature-soracard-api/build.gradle.kts +++ b/feature-soracard-api/build.gradle.kts @@ -22,6 +22,8 @@ android { jvmTarget = "17" } + packaging { resources.excludes.add("META-INF/*") } + namespace = "jp.co.soramitsu.feature_soracard_api" } diff --git a/feature-soracard-impl/build.gradle.kts b/feature-soracard-impl/build.gradle.kts index c0f1572e11..926e592f8b 100644 --- a/feature-soracard-impl/build.gradle.kts +++ b/feature-soracard-impl/build.gradle.kts @@ -33,6 +33,9 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } + namespace = "jp.co.soramitsu.feature_soracard_impl" } diff --git a/feature-splash/build.gradle b/feature-splash/build.gradle index ddfbcaf7bc..d1218da597 100644 --- a/feature-splash/build.gradle +++ b/feature-splash/build.gradle @@ -28,6 +28,9 @@ android { jvmTarget = '17' } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.splash' } diff --git a/feature-staking-api/build.gradle b/feature-staking-api/build.gradle index c1d3f689a8..0337f4f66c 100644 --- a/feature-staking-api/build.gradle +++ b/feature-staking-api/build.gradle @@ -23,6 +23,9 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_staking_api' } diff --git a/feature-staking-impl/build.gradle b/feature-staking-impl/build.gradle index 1969bd1767..c247bffb58 100644 --- a/feature-staking-impl/build.gradle +++ b/feature-staking-impl/build.gradle @@ -35,6 +35,9 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_staking_impl' } diff --git a/feature-success-impl/build.gradle.kts b/feature-success-impl/build.gradle.kts index 91c9df2f50..2b382107cf 100644 --- a/feature-success-impl/build.gradle.kts +++ b/feature-success-impl/build.gradle.kts @@ -30,6 +30,9 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } + namespace = "jp.co.soramitsu.feature_success_impl" } diff --git a/feature-wallet-api/build.gradle b/feature-wallet-api/build.gradle index cbfc62e2bb..0004a9ba85 100644 --- a/feature-wallet-api/build.gradle +++ b/feature-wallet-api/build.gradle @@ -26,6 +26,9 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_wallet_api' } diff --git a/feature-wallet-impl/build.gradle b/feature-wallet-impl/build.gradle index f7c00c4f73..a94b4caabd 100644 --- a/feature-wallet-impl/build.gradle +++ b/feature-wallet-impl/build.gradle @@ -55,6 +55,9 @@ android { composeOptions { kotlinCompilerExtensionVersion composeCompilerVersion } + + packaging { resources.excludes.add("META-INF/*") } + namespace 'jp.co.soramitsu.feature_wallet_impl' } diff --git a/feature-walletconnect-api/build.gradle.kts b/feature-walletconnect-api/build.gradle.kts index 1c588fae23..1cfd469754 100644 --- a/feature-walletconnect-api/build.gradle.kts +++ b/feature-walletconnect-api/build.gradle.kts @@ -22,6 +22,8 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-walletconnect-impl/build.gradle.kts b/feature-walletconnect-impl/build.gradle.kts index 534e1b6b4e..5d6e375ac3 100644 --- a/feature-walletconnect-impl/build.gradle.kts +++ b/feature-walletconnect-impl/build.gradle.kts @@ -30,6 +30,8 @@ android { kotlinOptions { jvmTarget = "17" } + + packaging { resources.excludes.add("META-INF/*") } } dependencies { From 2f63a4af272521e36c64751e3c8bd605e94b3b16 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 17 Jun 2024 13:24:47 +0500 Subject: [PATCH 21/84] add pools dependency --- build.gradle | 1 + common/build.gradle | 2 +- core-api/build.gradle | 2 +- feature-account-api/build.gradle | 4 ++-- feature-account-impl/build.gradle | 4 ++-- feature-nft-api/build.gradle.kts | 4 +++- feature-nft-impl/build.gradle.kts | 4 +++- feature-onboarding-impl/build.gradle | 2 +- feature-wallet-impl/build.gradle | 4 ++-- feature-walletconnect-impl/build.gradle.kts | 4 +++- runtime/build.gradle | 2 +- .../jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt | 8 +++++++- 12 files changed, 27 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index ae9604dcd6..9c1af224e1 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ buildscript { withoutBasic = { exclude group: 'jp.co.soramitsu.xnetworking', module: 'basic' } withoutJna = { exclude group: 'net.java.dev.jna' } withoutJavaWS = { exclude module: 'java-websocket-lib' } + withoutAndroidFoundation = { exclude module: 'android-foundation' } } repositories { diff --git a/common/build.gradle b/common/build.gradle index da98f04683..db536e857e 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -163,5 +163,5 @@ dependencies { testImplementation libs.mockito.inline testImplementation project(':test-shared') - api libs.sharedFeaturesCoreDep + api libs.sharedFeaturesCoreDep, withoutAndroidFoundation } \ No newline at end of file diff --git a/core-api/build.gradle b/core-api/build.gradle index 03af2589c2..629d01e93e 100644 --- a/core-api/build.gradle +++ b/core-api/build.gradle @@ -27,5 +27,5 @@ android { dependencies { implementation libs.coroutines.core - implementation libs.sharedFeaturesCoreDep + implementation libs.sharedFeaturesCoreDep, withoutAndroidFoundation } \ No newline at end of file diff --git a/feature-account-api/build.gradle b/feature-account-api/build.gradle index a6f48055f0..ccdc52c612 100644 --- a/feature-account-api/build.gradle +++ b/feature-account-api/build.gradle @@ -48,6 +48,6 @@ dependencies { api project(':core-api') - api libs.sharedFeaturesCoreDep - api libs.sharedFeaturesBackupDep + api libs.sharedFeaturesCoreDep, withoutAndroidFoundation + api libs.sharedFeaturesBackupDep, withoutAndroidFoundation } \ No newline at end of file diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 2c994d1344..e4aa37f482 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -94,8 +94,8 @@ dependencies { implementation libs.gson - api libs.sharedFeaturesCoreDep + api libs.sharedFeaturesCoreDep, withoutAndroidFoundation - implementation libs.sharedFeaturesBackupDep + implementation libs.sharedFeaturesBackupDep, withoutAndroidFoundation implementation libs.web3jDep } \ No newline at end of file diff --git a/feature-nft-api/build.gradle.kts b/feature-nft-api/build.gradle.kts index 905789ff5a..7d7422ced1 100644 --- a/feature-nft-api/build.gradle.kts +++ b/feature-nft-api/build.gradle.kts @@ -32,5 +32,7 @@ dependencies { implementation("javax.inject:javax.inject:1") implementation(libs.bundles.coroutines) - implementation(libs.sharedFeaturesCoreDep) + implementation(libs.sharedFeaturesCoreDep) { + exclude(module = "android-foundation") + } } \ No newline at end of file diff --git a/feature-nft-impl/build.gradle.kts b/feature-nft-impl/build.gradle.kts index bf735dd990..a689aa46ef 100644 --- a/feature-nft-impl/build.gradle.kts +++ b/feature-nft-impl/build.gradle.kts @@ -53,7 +53,9 @@ dependencies { implementation(libs.bundles.compose) implementation(libs.fragmentKtx) implementation(libs.material) - implementation(libs.sharedFeaturesCoreDep) + implementation(libs.sharedFeaturesCoreDep) { + exclude(module = "android-foundation") + } implementation(libs.retrofit) implementation(libs.gson) implementation(libs.web3jDep) { diff --git a/feature-onboarding-impl/build.gradle b/feature-onboarding-impl/build.gradle index 4941c37862..8b0602d5ae 100644 --- a/feature-onboarding-impl/build.gradle +++ b/feature-onboarding-impl/build.gradle @@ -85,5 +85,5 @@ dependencies { implementation libs.zxing.core implementation libs.zxing.embedded - implementation libs.sharedFeaturesBackupDep + implementation libs.sharedFeaturesBackupDep, withoutAndroidFoundation } diff --git a/feature-wallet-impl/build.gradle b/feature-wallet-impl/build.gradle index a94b4caabd..76313276bf 100644 --- a/feature-wallet-impl/build.gradle +++ b/feature-wallet-impl/build.gradle @@ -136,8 +136,8 @@ dependencies { implementation libs.navigation.fragment.ktx implementation libs.navigation.ui.ktx - implementation libs.sharedFeaturesXcmDep - implementation libs.sharedFeaturesBackupDep + implementation libs.sharedFeaturesXcmDep, withoutAndroidFoundation + implementation libs.sharedFeaturesBackupDep, withoutAndroidFoundation implementation libs.web3jDep } \ No newline at end of file diff --git a/feature-walletconnect-impl/build.gradle.kts b/feature-walletconnect-impl/build.gradle.kts index 5d6e375ac3..a1ef329282 100644 --- a/feature-walletconnect-impl/build.gradle.kts +++ b/feature-walletconnect-impl/build.gradle.kts @@ -40,7 +40,9 @@ dependencies { implementation(libs.bundles.compose) implementation(libs.fragmentKtx) implementation(libs.material) - implementation(libs.sharedFeaturesCoreDep) + implementation(libs.sharedFeaturesCoreDep) { + exclude(module = "android-foundation") + } implementation(libs.web3jDep) diff --git a/runtime/build.gradle b/runtime/build.gradle index 32dd1f23d9..091e5742dd 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -71,7 +71,7 @@ dependencies { androidTestImplementation libs.rules androidTestImplementation libs.ext.junit - api libs.sharedFeaturesCoreDep + api libs.sharedFeaturesCoreDep, withoutAndroidFoundation implementation libs.web3jDep } \ No newline at end of file diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt index 3fdf02bad6..1ce066788a 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt @@ -9,6 +9,7 @@ import jp.co.soramitsu.common.utils.diffed import jp.co.soramitsu.common.utils.failure import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.models.ChainsMetaAccount import jp.co.soramitsu.core.models.IChain import jp.co.soramitsu.core.runtime.ChainConnection import jp.co.soramitsu.core.runtime.IChainRegistry @@ -38,6 +39,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -218,6 +220,10 @@ class ChainRegistry @Inject constructor( return getChain(chainId).assetsById[chainAssetId] } + override fun chainsAccountFlow(metaAccountId: Long): Flow { + TODO("Not yet implemented") + } + override suspend fun getChain(chainId: ChainId): Chain { return chainsRepository.getChain(chainId) } @@ -280,7 +286,7 @@ class ChainRegistry @Inject constructor( override fun getConnection(chainId: String) = connectionPool.getConnectionOrThrow(chainId) - override suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) + suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) @Deprecated( "Since we have ethereum chains, which don't have runtime, we must use the function with nullable return value", ReplaceWith("getRuntimeOrNull(chainId)") From f268c7b48f29d1699d57742abe02201fd86c5d63 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 18 Jun 2024 14:42:48 +0500 Subject: [PATCH 22/84] clean warnings --- app/build.gradle | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 19b614d190..9ef09b26c7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,4 @@ +//file:noinspection GroovyImplicitNullArgumentCall import com.github.triplet.gradle.androidpublisher.ReleaseStatus apply plugin: 'com.android.application' @@ -229,8 +230,8 @@ dependencies { androidTestImplementation libs.ext.junit } -task printVersion { - doLast { - println "versionName:${computeVersionName()}" - } +tasks.register('printVersion') { + doLast { + println "versionName:${computeVersionName()}" + } } From 92b87e7d56474c5b5827ada676a291bff1a1109f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 19 Jun 2024 14:54:22 +0500 Subject: [PATCH 23/84] analyzer small fixes --- app/src/main/AndroidManifest.xml | 2 ++ .../java/jp/co/soramitsu/common/io/MainThreadExecutor.kt | 3 +-- .../co/soramitsu/common/utils/DoubleClickPreventUtils.kt | 4 ++-- common/src/main/java/jp/co/soramitsu/common/utils/Ext.kt | 3 +-- .../src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt | 3 ++- common/src/main/res/values/strings.xml | 2 +- core-db/build.gradle | 2 +- .../java/jp/co/soramitsu/coredb/migrations/V2Migration.kt | 8 ++++---- .../impl/presentation/collection/CollectionNFTsScreen.kt | 4 ++-- .../soramitsu/onboarding/impl/welcome/PagerIndicator.kt | 4 ++-- .../api/presentation/mixin/fee/FeeLoaderProvider.kt | 2 +- gradle/libs.versions.toml | 2 -- 12 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0fd020524a..7ed72a41b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/common/src/main/java/jp/co/soramitsu/common/io/MainThreadExecutor.kt b/common/src/main/java/jp/co/soramitsu/common/io/MainThreadExecutor.kt index 3f9abb73f2..8f10c78763 100644 --- a/common/src/main/java/jp/co/soramitsu/common/io/MainThreadExecutor.kt +++ b/common/src/main/java/jp/co/soramitsu/common/io/MainThreadExecutor.kt @@ -2,14 +2,13 @@ package jp.co.soramitsu.common.io import android.os.Handler import android.os.Looper -import androidx.annotation.NonNull import java.util.concurrent.Executor class MainThreadExecutor : Executor { private val mainThreadHandler = Handler(Looper.getMainLooper()) - override fun execute(@NonNull command: Runnable) { + override fun execute(command: Runnable) { mainThreadHandler.post(command) } } diff --git a/common/src/main/java/jp/co/soramitsu/common/utils/DoubleClickPreventUtils.kt b/common/src/main/java/jp/co/soramitsu/common/utils/DoubleClickPreventUtils.kt index c5eedb728e..28961c4dd7 100644 --- a/common/src/main/java/jp/co/soramitsu/common/utils/DoubleClickPreventUtils.kt +++ b/common/src/main/java/jp/co/soramitsu/common/utils/DoubleClickPreventUtils.kt @@ -3,14 +3,14 @@ package jp.co.soramitsu.common.utils import android.os.SystemClock import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember private const val DisableClickTime = 1000L @Composable fun rememberLastClickTime(): MutableState { - return remember { mutableStateOf(0L) } + return remember { mutableLongStateOf(0L) } } fun onSingleClick( diff --git a/common/src/main/java/jp/co/soramitsu/common/utils/Ext.kt b/common/src/main/java/jp/co/soramitsu/common/utils/Ext.kt index 052e7b202b..aa279aa606 100644 --- a/common/src/main/java/jp/co/soramitsu/common/utils/Ext.kt +++ b/common/src/main/java/jp/co/soramitsu/common/utils/Ext.kt @@ -54,8 +54,7 @@ fun Context.showBrowser(link: String) { fun Context.createSendEmailIntent(targetEmail: String, title: String) { val emailIntent = Intent(Intent.ACTION_SENDTO).apply { putExtra(Intent.EXTRA_EMAIL, targetEmail) - type = "message/rfc822" - data = Uri.parse("mailto:$targetEmail") + setDataAndType(Uri.parse("mailto:$targetEmail"), "message/rfc822") } startActivity(Intent.createChooser(emailIntent, title)) } diff --git a/common/src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt b/common/src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt index a177cea20f..99305792d9 100644 --- a/common/src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt +++ b/common/src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt @@ -20,6 +20,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.LayoutRes import androidx.annotation.StyleableRes import androidx.core.content.ContextCompat +import androidx.core.widget.TextViewCompat import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -170,7 +171,7 @@ fun RecyclerView.findFirstVisiblePosition(): Int { fun TextView.setCompoundDrawableTint(@ColorRes tintRes: Int) { val tintColor = context.getColor(tintRes) - compoundDrawableTintList = ColorStateList.valueOf(tintColor) + TextViewCompat.setCompoundDrawableTintList(this, ColorStateList.valueOf(tintColor)) } fun TextView.setTextOrHide(newText: String?) { diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 6cc1ac10e9..85a13e3f20 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1172,7 +1172,7 @@ Minimal balance Confirm transfer Your transfer will fail since the final amount on the destination account will be less than the minimal balance. Please try to increase the amount. - Your transfer will fail since the final amount (%s) on the destination account will be less than the minimal balance (%s). Please increase the amount by %s + Your transfer will fail since the final amount (%1$s) on the destination account will be less than the minimal balance (%2$s). Please increase the amount by %3$s Insufficient Ethereum balance in the recipient\'s account prevents the completion of ERC20 token transfer. Please ensure the receiver has enough Ethereum to proceed with the transfer. Your transfer will remove the account from blockstore since it will make the total balance lower than the minimal balance. The transfer will remove the account diff --git a/core-db/build.gradle b/core-db/build.gradle index de509b4b32..a17df28244 100644 --- a/core-db/build.gradle +++ b/core-db/build.gradle @@ -60,6 +60,6 @@ dependencies { androidTestImplementation libs.room.testing androidTestImplementation projects.testShared - testImplementation libs.junit.junit + testImplementation libs.junit testImplementation projects.testShared } \ No newline at end of file diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/V2Migration.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/V2Migration.kt index 2821e929ea..3465a5635c 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/V2Migration.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/V2Migration.kt @@ -104,13 +104,13 @@ class V2Migration( val cursor = database.query("SELECT * FROM users") return cursor.map { - val address = getString(getColumnIndex("address")) - val cryptoTypeOrdinal = getInt(getColumnIndex("cryptoType")) - val name = getString(getColumnIndex("username")) + val address = getString(getColumnIndexOrThrow("address")) + val cryptoTypeOrdinal = getInt(getColumnIndexOrThrow("cryptoType")) + val name = getString(getColumnIndexOrThrow("username")) MigratingAccount( address = address, - cryptoType = CryptoType.values()[cryptoTypeOrdinal], + cryptoType = CryptoType.entries[cryptoTypeOrdinal], name = name ) } diff --git a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/collection/CollectionNFTsScreen.kt b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/collection/CollectionNFTsScreen.kt index d89097676a..3469143d42 100644 --- a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/collection/CollectionNFTsScreen.kt +++ b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/presentation/collection/CollectionNFTsScreen.kt @@ -174,7 +174,7 @@ private fun CollectionNFTsScreen( LaunchedEffect(firstVisibleItemOffsetAsState) { snapshotFlow { firstVisibleItemOffsetAsState.value } - .onEach { offset -> savableOffset.value = offset } + .onEach { offset -> savableOffset.intValue = offset } .flowOn(Dispatchers.Default) .launchIn(this) } @@ -222,7 +222,7 @@ private fun CollectionNFTsScreen( return@LaunchedEffect } - lazyGridState.scrollToItem(index, savableOffset.value) + lazyGridState.scrollToItem(index, savableOffset.intValue) } } diff --git a/feature-onboarding-impl/src/main/java/jp/co/soramitsu/onboarding/impl/welcome/PagerIndicator.kt b/feature-onboarding-impl/src/main/java/jp/co/soramitsu/onboarding/impl/welcome/PagerIndicator.kt index d87bd6c0c9..88c612479d 100644 --- a/feature-onboarding-impl/src/main/java/jp/co/soramitsu/onboarding/impl/welcome/PagerIndicator.kt +++ b/feature-onboarding-impl/src/main/java/jp/co/soramitsu/onboarding/impl/welcome/PagerIndicator.kt @@ -17,7 +17,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -146,7 +146,7 @@ fun lerp(start: Float, stop: Float, fraction: Float): Float { @Composable private fun PreviewSlidingPagerIndicator() { val ci = remember { - mutableStateOf(0) + mutableIntStateOf(0) } val d: List = ArrayDeque() diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/mixin/fee/FeeLoaderProvider.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/mixin/fee/FeeLoaderProvider.kt index d58c5c4c32..22aa03fe7b 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/mixin/fee/FeeLoaderProvider.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/mixin/fee/FeeLoaderProvider.kt @@ -66,7 +66,7 @@ class FeeLoaderProvider( FeeStatus.Error } value?.let { - feeLiveData.postValue(value) + feeLiveData.postValue(it) } withContext(Dispatchers.Main) { onComplete?.invoke(value) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5bd03bef13..8635a4be9d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,7 +60,6 @@ wsVersion = "2.14" xNetworking = "0.2.5-temp7" zxingEmbeddedVersion = "4.3.0" zxingVersion = "3.5.3" -junitJunit = "4.12" [libraries] appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -177,7 +176,6 @@ web3jDep = { module = "org.web3j:core", version.ref = "web3j" } walletconnectBomDep = { module = "com.walletconnect:android-bom", version.ref = "walletconnectBom" } walletconnectCoreDep = { module = "com.walletconnect:android-core" } walletconnectWeb3WalletDep = { module = "com.walletconnect:web3wallet" } -junit-junit = { group = "junit", name = "junit", version.ref = "junitJunit" } [bundles] compose = [ From 548fca27ee8011b8924fd1d486db64cdd7388058 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 19 Jun 2024 17:11:18 +0500 Subject: [PATCH 24/84] clean --- build.gradle | 5 +++++ feature-account-api/build.gradle | 2 -- feature-account-impl/build.gradle | 2 -- feature-crowdloan-impl/build.gradle | 2 -- feature-nft-api/build.gradle.kts | 2 -- feature-nft-impl/build.gradle.kts | 2 -- feature-onboarding-api/build.gradle | 2 -- feature-onboarding-impl/build.gradle | 2 -- feature-polkaswap-api/build.gradle.kts | 2 -- feature-polkaswap-impl/build.gradle.kts | 2 -- feature-soracard-api/build.gradle.kts | 2 -- feature-soracard-impl/build.gradle.kts | 2 -- feature-splash/build.gradle | 2 -- feature-staking-api/build.gradle | 2 -- feature-staking-impl/build.gradle | 2 -- feature-success-impl/build.gradle.kts | 2 -- feature-wallet-api/build.gradle | 2 -- feature-wallet-impl/build.gradle | 3 +-- feature-walletconnect-api/build.gradle.kts | 2 -- feature-walletconnect-impl/build.gradle.kts | 2 -- 20 files changed, 6 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 9c1af224e1..c17b46cf72 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,11 @@ allprojects { } } + configurations { + cleanedAnnotations + implementation.exclude group: 'com.intellij' , module:'annotations' + } + configurations.configureEach { resolutionStrategy { // add dependency substitution rules diff --git a/feature-account-api/build.gradle b/feature-account-api/build.gradle index ccdc52c612..ae3ccf8f23 100644 --- a/feature-account-api/build.gradle +++ b/feature-account-api/build.gradle @@ -28,8 +28,6 @@ android { jvmTarget = '17' } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_account_api' } diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index e4aa37f482..e70a931926 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -35,8 +35,6 @@ android { kotlinCompilerExtensionVersion composeCompilerVersion } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_account_impl' } diff --git a/feature-crowdloan-impl/build.gradle b/feature-crowdloan-impl/build.gradle index 72a3bb441c..b905b6df05 100644 --- a/feature-crowdloan-impl/build.gradle +++ b/feature-crowdloan-impl/build.gradle @@ -30,8 +30,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_crowdloan_impl' } diff --git a/feature-nft-api/build.gradle.kts b/feature-nft-api/build.gradle.kts index 7d7422ced1..400f5532c1 100644 --- a/feature-nft-api/build.gradle.kts +++ b/feature-nft-api/build.gradle.kts @@ -21,8 +21,6 @@ android { kotlinOptions { jvmTarget = "17" } - - packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-nft-impl/build.gradle.kts b/feature-nft-impl/build.gradle.kts index a689aa46ef..72114dcc2d 100644 --- a/feature-nft-impl/build.gradle.kts +++ b/feature-nft-impl/build.gradle.kts @@ -41,8 +41,6 @@ android { kotlinOptions { jvmTarget = "17" } - - packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-onboarding-api/build.gradle b/feature-onboarding-api/build.gradle index b800e35472..784aff9b28 100644 --- a/feature-onboarding-api/build.gradle +++ b/feature-onboarding-api/build.gradle @@ -23,8 +23,6 @@ android { jvmTarget = '17' } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_onboarding_api' } diff --git a/feature-onboarding-impl/build.gradle b/feature-onboarding-impl/build.gradle index 8b0602d5ae..186c7e4625 100644 --- a/feature-onboarding-impl/build.gradle +++ b/feature-onboarding-impl/build.gradle @@ -46,8 +46,6 @@ android { kotlinCompilerExtensionVersion composeCompilerVersion } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_onboarding_impl' } diff --git a/feature-polkaswap-api/build.gradle.kts b/feature-polkaswap-api/build.gradle.kts index 5f0b01af70..0fd6ae5bfe 100644 --- a/feature-polkaswap-api/build.gradle.kts +++ b/feature-polkaswap-api/build.gradle.kts @@ -21,8 +21,6 @@ android { jvmTarget = "17" } - packaging { resources.excludes.add("META-INF/*") } - namespace = "jp.co.soramitsu.feature_polkaswap_api" } diff --git a/feature-polkaswap-impl/build.gradle.kts b/feature-polkaswap-impl/build.gradle.kts index 5eb6babd8e..bcf616bc8c 100644 --- a/feature-polkaswap-impl/build.gradle.kts +++ b/feature-polkaswap-impl/build.gradle.kts @@ -31,8 +31,6 @@ android { jvmTarget = "17" } - packaging { resources.excludes.add("META-INF/*") } - namespace = "jp.co.soramitsu.feature_polkaswap_impl" } diff --git a/feature-soracard-api/build.gradle.kts b/feature-soracard-api/build.gradle.kts index 54e9aa4d39..ada3c686d6 100644 --- a/feature-soracard-api/build.gradle.kts +++ b/feature-soracard-api/build.gradle.kts @@ -22,8 +22,6 @@ android { jvmTarget = "17" } - packaging { resources.excludes.add("META-INF/*") } - namespace = "jp.co.soramitsu.feature_soracard_api" } diff --git a/feature-soracard-impl/build.gradle.kts b/feature-soracard-impl/build.gradle.kts index 926e592f8b..2c8267b8bb 100644 --- a/feature-soracard-impl/build.gradle.kts +++ b/feature-soracard-impl/build.gradle.kts @@ -34,8 +34,6 @@ android { jvmTarget = "17" } - packaging { resources.excludes.add("META-INF/*") } - namespace = "jp.co.soramitsu.feature_soracard_impl" } diff --git a/feature-splash/build.gradle b/feature-splash/build.gradle index d1218da597..8e90fe0a34 100644 --- a/feature-splash/build.gradle +++ b/feature-splash/build.gradle @@ -29,8 +29,6 @@ android { jvmTarget = '17' } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.splash' } diff --git a/feature-staking-api/build.gradle b/feature-staking-api/build.gradle index 0337f4f66c..f22d4ef9d5 100644 --- a/feature-staking-api/build.gradle +++ b/feature-staking-api/build.gradle @@ -24,8 +24,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_staking_api' } diff --git a/feature-staking-impl/build.gradle b/feature-staking-impl/build.gradle index c247bffb58..87064197fb 100644 --- a/feature-staking-impl/build.gradle +++ b/feature-staking-impl/build.gradle @@ -36,8 +36,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_staking_impl' } diff --git a/feature-success-impl/build.gradle.kts b/feature-success-impl/build.gradle.kts index 2b382107cf..66a882b68e 100644 --- a/feature-success-impl/build.gradle.kts +++ b/feature-success-impl/build.gradle.kts @@ -31,8 +31,6 @@ android { jvmTarget = "17" } - packaging { resources.excludes.add("META-INF/*") } - namespace = "jp.co.soramitsu.feature_success_impl" } diff --git a/feature-wallet-api/build.gradle b/feature-wallet-api/build.gradle index 0004a9ba85..945bea7627 100644 --- a/feature-wallet-api/build.gradle +++ b/feature-wallet-api/build.gradle @@ -27,8 +27,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_wallet_api' } diff --git a/feature-wallet-impl/build.gradle b/feature-wallet-impl/build.gradle index 76313276bf..67ac0c5e9c 100644 --- a/feature-wallet-impl/build.gradle +++ b/feature-wallet-impl/build.gradle @@ -56,8 +56,6 @@ android { kotlinCompilerExtensionVersion composeCompilerVersion } - packaging { resources.excludes.add("META-INF/*") } - namespace 'jp.co.soramitsu.feature_wallet_impl' } @@ -136,6 +134,7 @@ dependencies { implementation libs.navigation.fragment.ktx implementation libs.navigation.ui.ktx +// implementation libs.sharedFeaturesCoreDep, withoutAndroidFoundation implementation libs.sharedFeaturesXcmDep, withoutAndroidFoundation implementation libs.sharedFeaturesBackupDep, withoutAndroidFoundation diff --git a/feature-walletconnect-api/build.gradle.kts b/feature-walletconnect-api/build.gradle.kts index 1cfd469754..1c588fae23 100644 --- a/feature-walletconnect-api/build.gradle.kts +++ b/feature-walletconnect-api/build.gradle.kts @@ -22,8 +22,6 @@ android { kotlinOptions { jvmTarget = "17" } - - packaging { resources.excludes.add("META-INF/*") } } dependencies { diff --git a/feature-walletconnect-impl/build.gradle.kts b/feature-walletconnect-impl/build.gradle.kts index a1ef329282..c79b43d820 100644 --- a/feature-walletconnect-impl/build.gradle.kts +++ b/feature-walletconnect-impl/build.gradle.kts @@ -30,8 +30,6 @@ android { kotlinOptions { jvmTarget = "17" } - - packaging { resources.excludes.add("META-INF/*") } } dependencies { From 3971dd5a6259ec4d1fe50b29d8b963d46f7d3f0b Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 21 Jun 2024 14:16:01 +0500 Subject: [PATCH 25/84] deps for pools library part --- .../app/root/navigation/Navigator.kt | 7 ++++ .../api/presentation/PolkaswapRouter.kt | 2 + feature-polkaswap-impl/build.gradle.kts | 10 +++++ .../impl/di/PolkaswapFeatureBindModule.kt | 40 ++++++++++++++++++- .../swap_tokens/SwapTokensContent.kt | 18 +++++++-- .../swap_tokens/SwapTokensViewModel.kt | 5 +++ gradle/libs.versions.toml | 1 + 7 files changed, 79 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index d934bf4d6e..279d1a0032 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -78,6 +78,7 @@ import jp.co.soramitsu.nft.navigation.NFTRouter import jp.co.soramitsu.onboarding.impl.OnboardingRouter import jp.co.soramitsu.onboarding.impl.welcome.WelcomeFragment import jp.co.soramitsu.onboarding.impl.welcome.select_import_mode.SelectImportModeDialog +import jp.co.soramitsu.polkaswap.PoolsRoutes import jp.co.soramitsu.polkaswap.api.presentation.PolkaswapRouter import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsParcelModel import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsViewState @@ -1516,4 +1517,10 @@ class Navigator : override fun openScoreDetailsScreen(metaId: Long) { navController?.navigate(R.id.scoreDetailsFragment, ScoreDetailsFragment.getBundle(metaId)) } + + override fun openPools(chainId: ChainId) { + navController?.navigate( + "${PoolsRoutes.POOLS_EXPLORE_SCREEN}/$chainId" + ) + } } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt index 82a431a068..a88b1786b0 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt @@ -38,4 +38,6 @@ interface PolkaswapRouter { fun openPolkaswapDisclaimerFromMainScreen() fun openWebViewer(title: String, url: String) + + fun openPools(chainId: ChainId) } diff --git a/feature-polkaswap-impl/build.gradle.kts b/feature-polkaswap-impl/build.gradle.kts index bcf616bc8c..b1797f9e56 100644 --- a/feature-polkaswap-impl/build.gradle.kts +++ b/feature-polkaswap-impl/build.gradle.kts @@ -40,7 +40,17 @@ dependencies { implementation(libs.bundles.compose) implementation(libs.fragmentKtx) implementation(libs.material) + implementation(libs.room.runtime) + implementation(libs.xnetworking.basic) + implementation(libs.xnetworking.sorawallet) { + exclude(group = "jp.co.soramitsu.xnetworking", module = "basic") + } + + api(libs.sharedFeaturesPoolsDep) { + exclude(module = "android-foundation") + } + implementation(projects.coreDb) implementation(projects.common) implementation(projects.runtime) implementation(projects.featurePolkaswapApi) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index af6ad86882..7a238c911b 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.polkaswap.impl.di -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -10,6 +9,8 @@ import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher import jp.co.soramitsu.common.data.storage.Preferences +import jp.co.soramitsu.common.domain.NetworkStateService +import jp.co.soramitsu.common.mixin.api.UpdatesMixin import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor @@ -18,7 +19,16 @@ import jp.co.soramitsu.polkaswap.impl.domain.PolkaswapInteractorImpl import jp.co.soramitsu.runtime.di.REMOTE_STORAGE_SOURCE import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.core.rpc.RpcCalls +import jp.co.soramitsu.core.runtime.IChainRegistry +import jp.co.soramitsu.coredb.dao.AssetDao +import jp.co.soramitsu.coredb.dao.ChainDao +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainSyncService import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.connection.ConnectionPool +import jp.co.soramitsu.runtime.multiNetwork.connection.EthereumConnectionPool +import jp.co.soramitsu.runtime.multiNetwork.runtime.RuntimeProviderPool +import jp.co.soramitsu.runtime.multiNetwork.runtime.RuntimeSubscriptionPool +import jp.co.soramitsu.runtime.multiNetwork.runtime.RuntimeSyncService import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository @@ -57,4 +67,32 @@ class PolkaswapFeatureModule { chainsRepository ) } + + @Provides + @Singleton + fun provideChainRegistry( + runtimeProviderPool: RuntimeProviderPool, + chainConnectionPool: ConnectionPool, + runtimeSubscriptionPool: RuntimeSubscriptionPool, + chainDao: ChainDao, + chainSyncService: ChainSyncService, + runtimeSyncService: RuntimeSyncService, + updatesMixin: UpdatesMixin, + networkStateService: NetworkStateService, + ethereumConnectionPool: EthereumConnectionPool, + assetReadOnlyCache: AssetDao, + chainsRepository: ChainsRepository, + ): IChainRegistry = ChainRegistry( + runtimeProviderPool, + chainConnectionPool, + runtimeSubscriptionPool, + chainDao, + chainSyncService, + runtimeSyncService, + updatesMixin, + networkStateService, + ethereumConnectionPool, + assetReadOnlyCache, + chainsRepository + ) } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index 43df745c14..2e3471fa99 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -22,7 +22,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester @@ -30,7 +29,6 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import java.math.BigDecimal import jp.co.soramitsu.common.compose.component.AccentButton @@ -59,6 +57,8 @@ import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.feature_polkaswap_impl.R import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsViewState +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId data class SwapTokensContentViewState( val fromAmountInputViewState: AmountInputViewState, @@ -116,9 +116,10 @@ interface SwapTokensCallbacks { fun onQuickAmountInput(value: Double) fun onDisclaimerClick() + + fun onPoolsClick(chainId: ChainId) } -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SwapTokensContent( state: SwapTokensContentViewState, @@ -253,6 +254,16 @@ fun SwapTokensContent( MarginVertical(margin = 16.dp) } + AccentButton( + modifier = Modifier + .height(48.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = "POOLS", + onClick = { runCallback { callbacks.onPoolsClick(soraMainChainId) } } + ) + MarginVertical(margin = 8.dp) + AccentButton( modifier = Modifier .height(48.dp) @@ -425,6 +436,7 @@ fun SwapTokensContentPreview() { override fun networkFeeTooltipClick() {} override fun onQuickAmountInput(value: Double) {} override fun onDisclaimerClick() {} + override fun onPoolsClick(chainId: ChainId) {} } SwapTokensContent( diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt index fad0fe8d3b..b50a48638a 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt @@ -33,6 +33,7 @@ import jp.co.soramitsu.common.validation.NotEnoughResultedAmountToPayFeeExceptio import jp.co.soramitsu.common.validation.SpendInsufficientBalanceException import jp.co.soramitsu.common.validation.UnableToPayFeeException import jp.co.soramitsu.common.validation.WaitForFeeCalculationException +import jp.co.soramitsu.core.models.ChainId import jp.co.soramitsu.feature_polkaswap_impl.R import jp.co.soramitsu.polkaswap.api.domain.InsufficientLiquidityException import jp.co.soramitsu.polkaswap.api.domain.PathUnavailableException @@ -725,4 +726,8 @@ class SwapTokensViewModel @Inject constructor( fun setSoftKeyboardOpen(isOpen: Boolean) { isSoftKeyboardOpenFlow.value = isOpen } + + override fun onPoolsClick(chainId: ChainId) { + polkaswapRouter.openPools(chainId) + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8635a4be9d..5d64bf066a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -170,6 +170,7 @@ detekt-cli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = sharedFeaturesCoreDep = { module = "jp.co.soramitsu.shared_features:core", version.ref = "sharedFeaturesVersion" } sharedFeaturesXcmDep = { module = "jp.co.soramitsu.shared_features:xcm", version.ref = "sharedFeaturesVersion" } sharedFeaturesBackupDep = { module = "jp.co.soramitsu.shared_features:backup", version.ref = "sharedFeaturesVersion" } +sharedFeaturesPoolsDep = { module = "jp.co.soramitsu.shared_features:polkaswap", version.ref = "sharedFeaturesVersion" } beacon-android-sdk = { module = "com.github.airgap-it:beacon-android-sdk", version.ref = "beaconVersion" } web3jDep = { module = "org.web3j:core", version.ref = "web3j" } From 4f79815e88cee041c709dc7abcf4fa58f9a9c880 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 25 Jun 2024 10:42:50 +0500 Subject: [PATCH 26/84] add banners --- .../common/compose/component/AmountInput.kt | 13 -- .../common/compose/component/BannerDemeter.kt | 135 +++++++++++++++ .../compose/component/BannerLiquidityPools.kt | 160 ++++++++++++++++++ .../compose/component/BannerPageIndicator.kt | 44 +++++ .../soramitsu/common/compose/theme/Color.kt | 1 + .../res/drawable/demeter_banner_image.png | Bin 0 -> 18721 bytes common/src/main/res/values/strings.xml | 6 + .../swap_tokens/SwapTokensContent.kt | 99 +++++++++-- .../swap_tokens/SwapTokensViewModel.kt | 15 +- .../presentation/balance/list/WalletScreen.kt | 28 +-- .../presentation/balance/nft/list/NftList.kt | 3 - 11 files changed, 438 insertions(+), 66 deletions(-) create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/BannerDemeter.kt create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/BannerPageIndicator.kt create mode 100644 common/src/main/res/drawable/demeter_banner_image.png diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt index ff7e28761e..08d3ac0feb 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.common.compose.component -import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -36,7 +35,6 @@ import jp.co.soramitsu.common.compose.theme.black2 import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white24 -import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 import jp.co.soramitsu.common.utils.isZero import jp.co.soramitsu.ui_core.component.input.number.BasicNumberInput @@ -63,17 +61,6 @@ data class AmountInputViewState( fiatAmount = "$0", tokenAmount = BigDecimal.ZERO ) - - @Deprecated("use defaultObj with copy") - fun default(resourceManager: ResourceManager, @StringRes totalBalanceFormat: Int = R.string.common_balance_format): AmountInputViewState { - return AmountInputViewState( - tokenName = null, - tokenImage = null, - totalBalance = resourceManager.getString(totalBalanceFormat, "0"), - fiatAmount = "$0", - tokenAmount = BigDecimal.ZERO - ) - } } } diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerDemeter.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerDemeter.kt new file mode 100644 index 0000000000..09d07710b7 --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerDemeter.kt @@ -0,0 +1,135 @@ +package jp.co.soramitsu.common.compose.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +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.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +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 androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.colorFromHex +import jp.co.soramitsu.common.compose.theme.demeterYellow +import jp.co.soramitsu.common.utils.withNoFontPadding +import jp.co.soramitsu.ui_core.theme.customTypography + +@Composable +fun BannerDemeter( + onShowMoreClick: () -> Unit, + onCloseClick: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(139.dp) + .clip(RoundedCornerShape(15.dp)) + .background( + brush = Brush.linearGradient( + colorStops = listOfNotNull( + "#46a487".colorFromHex()?.let { 0.05f to it }, + "#46a487".colorFromHex()?.copy(alpha = 0.5F)?.let { 0.65f to it } + ).toTypedArray(), + start = Offset(0f, Float.POSITIVE_INFINITY), + end = Offset(Float.POSITIVE_INFINITY, 0f) + ) + ) + ) { + Image( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight().padding(top = 8.dp, bottom = 16.dp), + painter = painterResource(id = R.drawable.demeter_banner_image), + contentDescription = "", + contentScale = ContentScale.FillHeight + ) + NavigationIconButton( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(10.dp), + navigationIconResId = R.drawable.ic_cross_32, + onNavigationClick = onCloseClick + ) + + Column( + modifier = Modifier + .wrapContentSize() + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + val title = stringResource(R.string.banners_demeter_title) + val titleWords = title.split(" ") + val titleFirstWord = titleWords.firstOrNull() + val titleRemainWords = titleFirstWord?.let { title.removePrefix(it) } + val styledText = buildAnnotatedString { + withStyle(style = SpanStyle()) { + append(titleFirstWord) + } + withStyle(style = SpanStyle(color = demeterYellow)) { + append(titleRemainWords) + } + }.withNoFontPadding() + + Text( + modifier = Modifier + .wrapContentWidth(), + text = styledText, + style = MaterialTheme.customTypography.headline2, + color = Color.White + ) + MarginVertical(margin = 8.dp) + Text( + maxLines = 2, + modifier = Modifier + .wrapContentWidth(), + text = stringResource(R.string.banners_demeter_description), + style = MaterialTheme.customTypography.paragraphXS.copy(fontSize = 12.sp), + color = Color.White + ) + + MarginVertical(margin = 11.dp) + + ColoredButton( + modifier = Modifier.defaultMinSize(minWidth = 102.dp), + backgroundColor = demeterYellow, + onClick = onShowMoreClick + ) { + Text( + text = stringResource(R.string.common_show_more), + style = MaterialTheme.customTypography.headline2.copy(fontSize = 12.sp), + color = Color.White, + textAlign = TextAlign.Start + ) + } + } + } +} + +@Preview +@Composable +private fun BannerDemeterPreview() { + BannerDemeter( + onShowMoreClick = {}, + onCloseClick = {} + ) +} diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt new file mode 100644 index 0000000000..610af885c5 --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt @@ -0,0 +1,160 @@ +package jp.co.soramitsu.common.compose.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.CircleShape +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.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import jp.co.soramitsu.common.R +import jp.co.soramitsu.common.compose.theme.black +import jp.co.soramitsu.common.compose.theme.colorAccentDark +import jp.co.soramitsu.common.compose.theme.colorFromHex +import jp.co.soramitsu.common.compose.theme.transparent +import jp.co.soramitsu.common.compose.theme.white40 +import jp.co.soramitsu.ui_core.theme.customTypography + +@Composable +fun BannerLiquidityPools( + onShowMoreClick: () -> Unit, + onCloseClick: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(139.dp) + .clip(RoundedCornerShape(15.dp)) + .background( + brush = Brush.linearGradient( + colorStops = listOfNotNull( + "#FF3B7B".colorFromHex()?.let { 0.05f to it }, + "#AB18B8".colorFromHex()?.copy(alpha = 0.4F)?.let { 0.65f to it } + ).toTypedArray(), + start = Offset(0f, Float.POSITIVE_INFINITY), + end = Offset(Float.POSITIVE_INFINITY, 0f) + ) + ) + ) { + HaloIconBannerPools( + modifier = Modifier + .scale(2.1f) + .align(Alignment.BottomEnd) + .offset(x = (-2).dp, y = (-1).dp), + iconRes = R.drawable.ic_polkaswap_logo, + color = colorAccentDark, + ) + NavigationIconButton( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(10.dp), + navigationIconResId = R.drawable.ic_cross_32, + onNavigationClick = onCloseClick + ) + + Column( + modifier = Modifier + .wrapContentSize() + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + Text( + modifier = Modifier + .wrapContentWidth(), + text = stringResource(R.string.banners_liquidity_pools_title), + style = MaterialTheme.customTypography.headline2, + color = Color.White + ) + MarginVertical(margin = 8.dp) + Text( + maxLines = 2, + modifier = Modifier + .wrapContentWidth(), + text = stringResource(R.string.banners_liquidity_pools_description), + style = MaterialTheme.customTypography.paragraphXS.copy(fontSize = 12.sp), + color = Color.White + ) + + MarginVertical(margin = 11.dp) + + ColoredButton( + modifier = Modifier.defaultMinSize(minWidth = 102.dp), + backgroundColor = Color.Unspecified, + border = BorderStroke(1.dp, white40), + onClick = onShowMoreClick + ) { + Text( + text = stringResource(R.string.common_show_details), + style = MaterialTheme.customTypography.headline2.copy(fontSize = 12.sp), + color = Color.White + ) + } + } + } +} + +@Composable +fun HaloIconBannerPools( + @DrawableRes iconRes: Int, + color: Color, + modifier: Modifier = Modifier, + background: Color = transparent, + contentPadding: PaddingValues = PaddingValues(0.dp) +) { + val gradientBrush = Brush.radialGradient( + colors = listOf(black, transparent) + ) + + val haloPadding = 16.dp + val haloWidth = 14.dp + val imageSize = 35.dp + val haloSize = imageSize + haloPadding * 2 + haloWidth * 2 + Box( + modifier + .size(haloSize) + .border(haloWidth, gradientBrush, CircleShape) + .padding(haloWidth) + .background(colorAccentDark.copy(alpha = 0.06f), CircleShape) + .padding(contentPadding) + ) { + Image( + modifier = Modifier + .size(imageSize) + .align(Alignment.Center), + res = iconRes, + tint = color + ) + } +} + +@Preview +@Composable +private fun BannerLiquidityPoolsPreview() { + BannerLiquidityPools( + onShowMoreClick = {}, + onCloseClick = {} + ) +} diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerPageIndicator.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerPageIndicator.kt new file mode 100644 index 0000000000..5a234c594c --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerPageIndicator.kt @@ -0,0 +1,44 @@ +package jp.co.soramitsu.common.compose.component + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.foundation.layout.wrapContentHeight +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import jp.co.soramitsu.common.compose.theme.white16 +import jp.co.soramitsu.common.compose.theme.white50 + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun BannerPageIndicator( + bannersCount: Int, + pagerState: PagerState +) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + repeat(bannersCount) { iteration -> + val color = if (pagerState.currentPage == iteration) white50 else white16 + Box( + modifier = Modifier + .padding(horizontal = 3.dp) + .clip(CircleShape) + .background(color) + .size(8.dp) + ) + } + } +} diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt b/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt index cc07271609..ceb32be104 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/theme/Color.kt @@ -74,6 +74,7 @@ val errorRed = Color(0xFFFF3B30) val warningOrange = Color(0xFFEE7700) val alertYellow = Color(0xFFEE7700) val warningYellow = Color(0xFFFFD600) +val demeterYellow = Color(0xFFEC7430) val transparent = Color(0xffffff) diff --git a/common/src/main/res/drawable/demeter_banner_image.png b/common/src/main/res/drawable/demeter_banner_image.png new file mode 100644 index 0000000000000000000000000000000000000000..9a50ef9fa54b863e5ad3d421b47fc119b5acb31f GIT binary patch literal 18721 zcmV)MK)An&P)U8?m7SS z?dLlJ4$yv2wF|N*3k&{2fH+D*&<7y068{qbTcm`mou1OwVhsea50QWav|p3_^V~-R zD}6OsPKzXL)hhVn$Hz6mQ9jH0dg)Hshsc5hwBM4vpr^&RGnYeaPYa~70KO-s43P0z z#P1~YJRvMZUJvYJ?JF1nX@O0Nn~7(_PWAk1t~f=n8&H;5eY5!lD*0Pyxx zl9w!RmudG5$g}|TzlbB5MzzkUP6l(5-reEm4*4Cx{je|50pRVY3Yd@Sv}_ zYV0>KOLPEu`x!~s{SvadL+HIAjwckS6G9ZFAN-Ab@%>lA43Og>G*+#t!Yt7N;O$3L z{rc%|0_nXMLi?5M2?CzA%vH_Rd(uxj@c3PkRI zNh|#(uu}r;v;dc_S1+)f&%yRL?lc@$wo7k-Y0~0SJq&f*y&brImD@AV!oV#WR>;WAB-abO|{P{V^4=#h6Sm=Y*5@g%jW&iIlgk1X((n02u zZbPjvU$_|ve ze^dKZ_CEKT(nIz4Ngvj>JagL>atwgQqLXpk=RbQ__trz+GiZ*PGx^jdGM71)jOBbf z1Ks)7rfojtnIE@~kCooBt+?n0m?1gFdzZevzGJdb{8M|r^?zZ}>KRL~Iv{HI5>YCX|Cu?L2DbB8 z6TV|_nh0R55)k4Db25=h2OJC|wkORGQvY7|d{L<3dppH{h@1DzKWkrJe?L*m^h-;m z&F5OJ_|)jeZI^bf`}+m-*>deOoor^MlTHbIg(`B>m%P%XZmgAC`{g(0ZoTsS@fTja zJe7lcH}AyK-5ojryuC!;cw_va=4Y&Zn087@Xby6rUn8+q(K%zTEC4aIb zSYM8CH}T;MhlP``n9q{~V`E;m@`h57z9GNv@;)n@Im=3?q@7OVrCb;QAD*oh z23pr%c4F?<+znFN4}c7=Lml+Kmk#>l-`8Ki?dHo5-jjU2KRi*3ozZ=3wyy}*yRqYr z#=$xiM`#`nYzNCU0XVD0Aih3{gZQ*1J$Vi@X7*L=N}rQ;>m|%P=7a=LP6^28vLcho za%MYggXm(xUzRt!x1F)t!w+e@H*L9{8OM?1)$$uC5TdS*_eF_co0x=Zq1Z*Dm%cxw zqeqO?O8-*tENuBf+o5OzZ5u!PGsCb4^amko7e4gQow>D_-ynRk0Y~Yx_|+2anGhl1 z)KWi_Fc}kJB7?pEM+nhR2-g5cFnYEXHr2XbE&w&}BAP}F$K>nFSEM8fcQwZWF z2`yiZm=)T{kfzxo_oFW@28d#BQw+Wbe?u|wF& zq?e^K8TY+QRzh!U2VarEXCZK_2)e-YFv5`$PA&`WEv>Mkv$rrHgS+0gevOmA<%%Oo z+w8sfhNIkceq!>R&;1UjLD?T&yJ#wLyx#+F#VhxnEz=$A@$E;jGnHAU{8yT#L~ol@ zIOcpFPXFUA5Z|f^dtLhGE8huwK;^$X@(^h|e>qwe7FwoC&>ITdCH37|m`c+gg-wG| zS}JQH==#tfr|kuluaZDVPG#Ha&81ons*j&q8F*S)spI&4+F4q~NZU5AB#!Bs4?(4Z zwn>^oaO!;~1FY|5GIC{SFL<_iJe{(C_v@kDGT{hs{x5HOv!4=~kuyI2Dole~@BD`@ zQ7X)xN*wQZz8_-~&)=#~2(!~whw72qQi2Zh5c^?!OWQ5m-!8~+?u zsOgX1^IBAsT&bKHh&_0_Z3Xw_Z~4mFEPD507fJs8iAx+|-&Cks>7u8#H^ES1eGU{$ z7RGvIT~3I^42ew%=<1yd?Oi<(&L^^JEmv8$j@!{)D?)3KS{grp)6pN=ivGK0G@)!a zy~NI@<@42|Jh-hBys-(Wj*la^D}bHLNH>=O7r7mBM)8uY!206A_cpFRZ`2Jew`JkN zKZa~7H+0HJ{sX4<@N2AENgi3WOV3Po$Ud#(EnWY^4!9pOmybA(p9Wzl4LOe^Y*#(Bf5r5U-lMV3-8UCNi<)Cx! ze0||6Ipx$-;`~5a&R6S{!Hc7iJGj0b#T|nyTc5b(&??Q9C^ksU^;|uqqgs-j_vJkdFX}HKUJCP zu-6rMlo3%1s*9vwL!K`J$4XDmIc%wmitvBOJ~|TzYNcaa+(u;gN>*7GgbQ2H%}2i1&qJ+D%6U`-4xSXg#za2>czdi}xaqSoJbqwR(Z9RnHGRcs_e;Zkt$pWf9h1k-_^cL%1wl z78KJTE4~UP!~{hr03hKUNr2B786kHQq5FhC;W0gnmqRLtp)9YW&e8Lbmo!@Hd5|k} z@IURixnnW@djIMu!r+xE4j!CC&bK(z)d|^rmszKw5?%0_>Dej>xWeXb_>5J0_&)rx z7ymAq?C;+g@6OB}UtW&aq{Pw&;AL+6%4#R@WZ?ssZid&Oy$ZagH{O`(&kp~kniioPH;Pg9uyCOM%Gqsd;+;siLI_ZBdu16n3sYaEw!4cV#im<3oK$$ z>@a6UE-G!*ck*+4kSoLzc-7fXP^}$`-1X4H*6qkCDxd`){6pb+IJ`OzBNa~xPh&*r zRsLpDRFQMtKX)bk=bWYB;siQ+=w@PN2@?~;R|rK=AoKvkVd#)R@TD}FL%5O6DV4Q$)$4H4cSqtF#qJ2V zleqsuxegfjE5GcYoP0nA(X^oiP}>u8#?JX{a@iy5blJ7}Gc7+DDV%fZ&tV$0*MQf0 z%az?b#K4zu_MOjSj~>#tAwRzlQtc@2EKBJ>sI!#%hqyC{{m%>~xSqB&w85zS8q#EP zAczbD5?+b8Y!x~VUIwYQR!HztRK!yqZBQ#rLUEwNznxqg6{pvRV7%gmw-8d)Wtylp zI^SD*BHXb2El|-#cJ5gIW;km275J;)u7gFB!wk5Nc}rDuNsm*UPKn?^b8XHdfIFP@ zob~kE_=#r^xBTbr^W%5(9Bbh=uMEfbE`(nWZiWD#iNvdoI~r`8s6P66GBi z4#j)lyKUolgb$qo*lZwl-1ARMWzp{(JNuIiI!Y`ZE{q+EPjig57LE-p`S7H*><6$5 zBQoo>;_hqG+s4Xw880(A+ zRfvp+_#?Ai)88o>Cn8JOn4uJ*vY~bHe8|k{U`rUSXynLM6i=hFuw^@oy@P`i+&cLT z%pV_wQn8349CbvmhGM$LuK^kbB(lrMLcX#11o-xn0|nYcA>^}`Q$0?6>OaC%rNzJh$wktJ^_vd+uTQR#(z@p7*OzfhyXP%D6z)c; zIbiwFzG5l1C4j=Ft)$N-?tfa>LbSi+lk1Lr$5Cj_f37sY@{irY;v-StO%0#+rw^0V z+L0Ul69oSnYOb2R){Vt;KKBvW1)6=1hgQzk@$x0Wpi^o}rn3`rD8fjbh{%P(qP5sR z5CSnytoZ~I>I%~|92Y_`JWlGvl%oz3iafhv&80-*zA!WL)OR>cHRp@jR#%>$sFNIb7470#aQ4ot4p8*NbG%~Z z>`Pw&m}TIxyF^r;#)dAWThZ)lTf89Q5-FiBS49INv`p(7rMOEIuvwNtf&Mcm3K<^6 zNkx=z76>|Cm_Ne2ig6}z8d6-WCFHgG4-E@s0v>)oXf zUh*(~Zn^EsYf&^_fJVfY{Jgmf>{Loka$_~)1>F{Hy^|hIlYerO^;wMB{s`Z1MJxXp zR1=OxDfGByd++iC`zq8|&xt^%`W$=q!{rZM@_m4*F;EQ32jMW(3TG9bF_Dbf6Sb&c zlmR&g!TM$gZfG50m{n zpgb~W8u#C@(<-$>A&rxZ8h3QyIX}TJI1b_wWHm0^%Xaa$Cf&)#$5>(Xq@q{spPVjzp1EG!w(*Yl6}h=^b%Bni$`M7P!w|45zkcVGbIo-|Srp z{mA7k?V6wvU3~+{SEk}+s{32OhbxNP0o8gHnV@_-3n0!}JRI!n^9$g*{9@H7*atTL z5$DzF>oOl4T7z*u%FEQ@ojr26QWD!rlTbxt$8Bq2eUXkOj-0}X1%Yzx#a9@>Ligpe zmJa`=iQyr1GtE~PI7d~Ei}$~J;r_U`nTRE^Ix;bKh%fx#SI@bmY1nMmfQP}s3y{4o z;7}G0klWf~R=_hSK);z1xrXWy5txC7Xrh^)P%)lBi_FQU@h?aIKeRR}SZ?te1=b0% zGg;R{eTD|wXJ>IhE*$pH5yy1$S;Yww09LL(idPf|hD{i)=`)f$XsoRrCXkani(yjp zJjCV5F-yNkx+7=|DJwbBXC-uxPr@TX3N6hdtRnY{l^hnj|6k{qBBz@N#u3Ta)2ajZ zuW9~yt#^I>tB#w>jH7M(u7wA|Pq%M`VRWHTf7)r~zpG%JmMvO*Hp);jRg;fs>xP0? znZO5X*Fa2k>xzq9nvjV)O z>u=0Vc>XyYlt-hAI+^s0NNfVfR!LC~DW>=H^Vu>~`VE({j89*4CA7>WcAn6BoroI= zp{}RCWVXtKgeQ}Ku23pV)PaPPNwMhW%nWj@QoHatii!|7nF@3iZfjjQ5A19PwC=zY zOx?#$&X|zYcMkf@)b)Li8a#*E!G%F5EU;VQe^{e%kv|M`K>09z;&j!J352Oa5S`buF(6;4K zxwFo-Q|VV}vOedP;EeWeXfL>;{H^JhHk%dTS*gsMh0>r^2ZbUvX4&C)WLc$9SHt)x zMgWw~2QdJ$*z^MkLkS6GPw~KdOF;q59AmzW>5&Tf)LFvhgzEkiv2&SI^S8>;049=e z#3@~_G%&>6(k0G~;9?g4Mf(t6cocQN^W7zI zyj6jpWxBw`aN|#DNK#o8GOj4`21AQZd)snhW9%EbF6H0dH#q=h*Omgiabn9hl@dl# zider4;k=-Z%Eo(1b|(4HSzV zlYe|+BCBWSfk$!9hi_XEepKIS78O>n)W6bzs0;}g^(~scRRUnCq?C7Vp;X+Ebv~WN zYkYH|J0ww#HQuHCkwu`CD$U!RXea!9v2%n(5hJ9l2AZ;6s4WSi(_IqFt}>Vij0nEK z3liMSG$wpcz;hhtqQrG*QgMb2#a+o*KtNF3mvpZId+p(F-`lUIhCPRXiGp93t zP6~lx?>m0Ewm{@;PI{Oiyh~Tu5+g*&$*G^IKj<84PZ@ov6!x1$?v2lCWji}KPfxjj zOz*~KWXQNXIJElrPNWw!xKTyx1XF$_TbC|Z{h7%Wh%X$J*3AN z3zGqZv&u@_5m?lNF(5cggv2c!Y61eR+{T1dWE={Z$|s_KhC1PB+!@M+VS?*AHC~9= z)s+4sP`Ni^J=J2do@1<7qeF7m3Vx>WUJ4B-a>$ZOVeZQLANV_gfj~(h<){aKu1*&D znLT`!K{Q?jX2}+2mCqBDs-!25&Ehc?)3?;K2IXqQ^P^6ks*H{SaxcE8WsExNdRinz zen<-6q0_!JG#u)&bS$=u(iTM&!7(~rVxnp&ir-t=+9yZf^_ehJrDOe79saPhs$4}m zA51bXd>@9HV0yXlS(eP}{0^O8cw4^}~A7LP*v?%aGXx}X-j#HncT2o%IIXt4|dTYCS=fBboqsA5OzhPQ2?g z!{oC=r_Bin*oFRt7|;0DF9y!|bOClk0%n@lt+R9T)%)8P&O14ZYbtR`otk2Qk`*Ne z#9n=ziDjIriAaS}gv@ILHDP~oq{S!+PML7iBo+528kKHmJ>#pfIVJ>Ewv(atXKxZ<=Ms=$23WZ8^*i%i1sch zVBc>njVGfi{K!^AbM?<7_~Y}~qz%|xuiym!jV-ee48HfH)3PsS9(YLkTd!D)qUVEY zG~W&A45y$Oo@&XlpG#=5Yd8zUlycS4p!Gx3T2uB{MDkV!p$+Ru4O9lVDVlgox7pC*@md^Qb^TrK)FVklc;KKDHoKX?$ zKXd}gwYKYDGWd>*wsIEs&HwnO;)23s^-+puG%HpuD!4<{i3!ww@;WXHiop;kK|s(< zrNIirI83pXrX-Tsk*dE7vsl!CoA(iD&3Ebw0f&OB=CJeN|DB2ocLBu&^S8EC&_LF18cLp>*m@*PJkf#Yz?V_VBeb zf*pu6HPzn24pqytBGC&X=8h1J#%jnZ%_}5kF}pSF3|oom_EzSe6R&R8#z7NKW;!$E zG%>@Z?l{9~Nf&h-ihQ^LvE~`aE2|mgU#n`)YbQfzqE1OA;M%d#ApMXmT&ewh6$DTP zf>xSIj9>@?FfA?MezisP z&|wEKlPF`G{&w`;AHU4Bq2q^FEp=yEbC_gGBE==^ZpsBz~QoD$S4todIG@CqyF7=@VyTS4jX6Gy3CW%E<$y z4Ti{F?LNcXgSUmJu%=~y8$p&_r+5KCI@6Vi1} zsE#IZd2|%2BjXKG6b=>A{fX;V{E(}oL!6AbaQ23IhOFyF7P;cGwxjkX_pn8j+F=k(J@iC%J8ftR0Qk{#&s#o`WZ{?s^EC3b`UEr z7>PC34NZ89tyEt*kZA%Ljtx@BUMo=8-mkz>&*kvC+$Y+_Q^Sd9K$Lt8HDOr{({Tu_ z!lrFd9h=ZkDWDDY)S|=sCK?Xdv#6g6W1Snk8Xq2rEb8dzb^qeV!%z&1Nok*iYWTl& z_2w?$ux9?!R zjaGZnq8swQr{}b(WKI@oQVpK0ho6irMH*xrbjkiT9}?iKSNwLuv=X{mf^{nq3)JqZ zPHuK>HZp7mqc>4R2{Fk2IcOqQ32c}GD6}tK#Gl7s-2%n! zJNV{yR;8vL+%g9}t`0mGl$4sRj3W%edRnTg5E&b?K|t~Ag(zjt_G-05+}74-^4)X( z>V<>fwP7pl9?g!ZwQcPzkGL9fG8+6$cPGs)Rk=q07U?c#ni;+%LtTp$VbU4VInA?> zvr3Q)QhJly(!yc4@cvLYzFM49$Vif9YU1>}%x9@#Sw?jP9}dL1~r~K6`<;-H^xWZ2QOR;}Ir1E)65Rf41?Ntpl9#EA?DRkDXucHKH zC6Wclddei}EJ_CwmKr2-;cx*ON_9%<`Y3oE&O1x*(Vid;6gqY(P8%3Ux#7GTn1K?f z9_IvdJd}ND^vXGc@pUE|XsV70>bPXF#=L-VkuFkW#&D# zBTQd679zPo=zDAz`4B05MX6uJnW!uV9n&nok0ywVDg;d$H=AvUoDi|DjD{NdN5lS7 z9n~Pd)kI74Pfifj4S|FwYm?AFId0;gq7^Yg82#CpY(V|isBgI5m_7bsQ4aFxd^Q1{7 znt7~20lb8I-(>$ln3bJKY!If$BK6Z)*X(2De(?Dhaw1#kpW9JGP?35eZG7ZFa5f5e zi|Q|nGi6eozys_N67Y_`74TGX>>C5sWj}(qY7R#g1a8T`sq`;@`y_rUn(y-8%O5T+ z=@i?G&%k@uG~G>Tb~v7GFAOJS!b8?bCL`D)4?WFP4ZwIVr_`(INnClAGH?)|vo&0t zktx>i)JlI~&=J=O7=#4KGJ}zlHpya>G3S&qW*77;xM=;_uyKc9xgJ)Z5sKB;@3))?E!C$DxaQvZPrZ{ru65U}$ztVC z)H22?`v7K`)<|i6?cQ(QgKyu_Ow3M6;T%@oZt8m^gVae*6^{c`N7>q;N^Tg3{A#q= zhV(?Ol(VIb(E!plNm*v!S+%OfH@!C*Cw>r$(3vD^W4g10Q|3vBo2vIoEK^2u3H_S! zm^IXC#8KizGJ}FRXxownknQR;z+|pRhM96zIbmiE86J7dg;RSM!(H27f-GgTgrXkh zK%mxJAT#{v)??m(qq#@sj;}5ksBj;D?A#sCZOI!wtgsV8(>ZyyNZ5j z5Haw{kn=&KAy43GUA%zHgs@tJIvatF4ktH^+tDU*(g~6bS#+zsr*|3rcw`ee7~-Vo zGJQ%097+sta_iQwBhzf9&8`CJKZSR8Iu1T~#Br>4bvkm%5LeXJ zE+s6m&WznBlgdK|M*k_18-!AqQs6vPp%Yn#TB7^S6-WpYW`mY`!uLUgClaC3AnAx%gh&qHoY#*D5v=-{P3t%MEh zi+-lH?VG8r`|Vc_JL9*o8#LpPCWcL<_|DmO;V=)$XKVZYE!NZgk*FBd34-@{wKOaP>5!qLwOzKP8s+#TH5M17yJ>wb1eURG>vY7pg$pui`A&IHDrxJg0@&=Lad^WM zmC_ZcDgm?(gT*x&I~<3VP1;;fVb8=^zxmo2ud+@rPEz}_#j4sTsRX`ClBl4|Q$p<4J3$@|s>dGvT@F=Fapee85UrNfO$~L3#1fpT+Q$Zz#qE+*UYGos z>`QEQU`|Taf40%xV%8B8kriF$>Z^!(#co0zyizMAZ; zb?fYV^P>lO#fk3iRq3=_+27WKuJi6|+Po2OrJwf8vQ`=_e(2K2==&K19xA7Qh7$j% zIB(;As(U3$a>T}dONdPp6(%i8BQFGtk&4ms#aM$R15U^o7c-#KIoxI;lF@_WI%Euv zyDk2wMN#CzD8Rd>08L?}aac|Fi-%l{BQyBdna#t9Ut`d5Wl-%mrII2X`vpp$DuzZ0 zIl0N@!YT6@`gz;>_0DtYZ7V#lvho*&p@p(korAsu8KeRzL1lj1yj&}qZWbJYkG`LZ!$s_-aahCMyb}5rG`dfC ztpz~6#Tvcp8_7oBU&FCYuP+;qpD~((P0b(?=g*-2Swa1hYQ)f%3}mghpgu}Q1T7|Q zL4P8}atm9waSRwjw@{47i3t|nf`m?0r`R_G3m;}GIE5iFh>l5oR|ino z83@(!=K4+Bv0D^Atmd<`3p%3KYz~g=SpW|VDn`>XzfXx&JpjCLoc6RB9iMWATn0g! z$Ee2;hS+$WwD14k5BwD<*|J)lQO~0-Yy^7|!FzGu$$W3?dp=&=S^M@_)Or?NZb@rp zC@6Fvi@1g?L;{JT&ZG_9Q#9kuZ*}?>03>xTVX|02?U-bVXb{!dUN)fH)NNQ*5{~(q)5gLXX zSW(Q=oUOiBZ80+gAX!8&PCL_7_a23yL*HY|yt(891}Zz$nae}5EKM~jK1Qj<6Kc&y zI-R%)lw2hRI2aTn9)Kmx@VGe#kr}; z6p^NKgRH9rHKY38%n^aBZRsMk0a9!Xq$v_nbFT>A9gccb z1G1G5mQu}Jr0%RSWX9xubM0JKnGbJ0xyD-h!trkfKllr5%6r)puBWb1 zqttP*Q>Z6?mSBo$<3IiICRHf}LoCfah``fm0c$@WeF%?>gR2g3d|WWa@sK?CbtD28 zxkPA(Au<$-gG1~9&iBm?6KkoR6bmllZ?$q|s<{qr7>8Y!Yl9~XBgi3LK3@hwBtjsv zuxPXm?Sey#RHEY3dwMGckievvbEdIMgAc;eV@-T{vCqx@dyU+&DIg(o)iiL& zy-1vSg5s3KD)lo>kka4?uOcHIj@C@%F$T-)d~I9DUq{$qM;STwb3czF`KWv+w7VI2 zaAX^d`?WePNb1rWpi5;WN%CF}Vk0tvZ{l%}xIyaW$!uw2{KSv#bnEyTi(D*xXkB(< z`?fcl2c>|t$_#E(YYBLjRGnF22FU ziHuY;FYENaP}CU9SL%A8M(2xe9vKu-JwkOYX;j6g9Au`6Q{}NAv)x@dsoNPumGN=j z35gfv;KZ>orpjqul(z0Fjz>`lB1&(fG%y+mPL0>x@Stovbfj|d%fT%$392j^JscJE zmSLp^NkEpm$Kpb1BSuKC^@8B$!MFa;ZLkY8W56p74=hDnXN48p0*TV9OO-(`Yfqpf zoq>w=Q1-1!%#2rl-&LN-$8)e7UyVPCL<84U!gFrXF>sbf*nK#Et?rNYTc zVKCaJi#Qblj-&%d9TZ6U+Ie`oG67GG52^5?akr4wN_K^*ljnvB(HuMrKaKzH4YR` zBVbFtc39|5WMU7b^*DZ0z{&p8t;fIrSFi^(b7@h+T_$WlS;a53Eb22tK$Y7^eWDho zc&lSL>5^8FX&|yIAmxU$aaV~pApzHAv5%3(uvu#ViwdtnFm;ECE6Sj`B*j7Pqh!ThvN|_KL_A`~kH}->wDL^cOEeIAMIKR2z zk=ws7T<2BrvDd7gP3ijldqdu^?SwGAe@GL32WRn%`@yI}Y zHwqEg7-H{v5f1)wjSrm-%z6tg;K`EaqmDi2y)`0O`oYxu`9CN39>5 zV(X!SW!i@Q#6^7YwThyc3w$G(>uC;JuPa;T06}szK;IsVQlYj+_7qdwAM||0p=hEJ_oY(2W*scVxf{qR zzmQkNCC8KvIRqLN^rqjGFV9i>ss?S3n}v7JSpdH-48lu(2``(Gsy?Ke!2wrls4311 z^4C&WKLO8w2;ubj#y3vwpW7XpG2pRUV8sho7!t8}%{Y{p;&o@Fj`3-Q{&-ETJLRuc z*$Z0u%^WhLmOD}iNxiCVr<&bG)X$aJ0jeT_)9_nUgSK8BZSs+TWA^nR<=Oz*iU(=Ni zNOTj({8$Ec!*h^}NUHU}TAac(Y-Q%ydjE=gqG#oBnil)4i)!De6sAT^Xhj4VxfsM} zz}Tx8w=DTHC99eg_*%Iz z>u>eolksE<`qV#PJU!o#66aigRw8#3Nke5C{DIm-i;isbA<`#!i&nITvpwC)$fBR! z*u90ILfXH_Cw|nI@;;1#{nl~B#`dw^U^u;{6D3DW)pI&5$9F{~kkYY*7x@k6|IH}P81USb*jSTd2%ow) z`Ab|`s#A`PB%JDa3mT>8RI|SsCXEdvW1LVxJqC2sraRuEDF&N2EBPfI&Z3umid0zJ z^EBQ~JroOem#BuZS&7!?WZ=Ja^uboI3XcQ@u(IvaQjSPHyY$}V!XkG3ZbzmvD_&Xu z6dD&OTJ6ok_s7saDtOWg7xrM}wc5V8>=XQEf-%Ra7;r&`~JjvQ_}>0;8*Eu7a!Qw}MeZJan1-+OEKmgVwFc zMXE9V1+WnzeqSJK_hzN@;D%+Z6Pw%hY*LD(EZG>T043i@j}-L<1mA? zTWozQ>U;3!HQ3m{fmx*bPJeB+doK|WtK@YI1U8Nv>-2KP)Rc!zd7)>v#bhwdJML)J zjF7pe#QMt_u_=^osl|*DnTfCu#O_;9(_wlEqf@n&tyf)T%GWv?TA@O3Ou`1Q zRNF;{g)^i9T};Jj)NkX-`*oSmQxNZkk#d@;(6o2dE95)De;ctSRVx!LynW2yztSJl+L&3nm)TmlRhrHDT3q&XeKil;j%ADHHY zAsDT2{|5EDh%#kF>{W&{LHV^}RiUIVVaAm}1GMaFp5WivXbLxOF@S6Kq|m4wig-sP z+mToc5Ob09 z(R>a$p{hb|4vvnG77jI@qklBH0F*)&f(#%5@)-C)14*Y%F#{7}^USeHD>o)q6UQ|O z01-YV#I(I+@Yj6F(!e)Gg^cwpV^^VaWFI)<)D?H#FLp0mWN*{%n^9Adul`F3_%wI4e0o;0Ugu{-~?VkGX@DU&L~11i(-?;f(+x|nj@CWVSfP~(ah`RC~g;aj{Xe0?Ud=BVv1?Y_$G3Y6rpjc%c59Dvp+m8 z6w@JxF()RUQ*#cf#kb<48mK{>ivq+ml=VmRrHDld)Ms-ss1t>;_x!(&0?&3dk|CC7 zWtKI8PB2w^Z&))gWSQZ@U~?j!_oOYw>WOncI|%y$%{<3jz1qV7{f3akHT@Hb&lXp8 z&!bm8&EYCFksCl4D)tIWdWo3S;@1FDvp4>lcABQ|R5U>v&J(Ct5mo^8%x*gY9~0_! zvAxo?0VI`oIS?{|t57Eu3NofmhUIDwN=XE1&JO04THFtsuPM?|5~Gi5&Zn)YP-j*8 zuDQF(ftH7&*7;G`PiW>j9@3JhfAS^t7kwUH>#nlN_tb?n%PDXj2Co$u;+7>JZ0STR zJz%U$nE<<1oI)1SGGGiS3!w7pal z%Q|jM#JeGOTo|*B85V^nSE@o61UKspqxv9<1Ga%BFT^(f zA9$VpbgxA-4!p&`J8HE^+ow!-Eyb?sA;@^vuxVaD0i~?`zV?XSWZu)Y|w$;M!a4eKKh?MxKc1b)yFGP%+B_vYB zHsa@m#kS4$4F@r<6_Kny01&N5rUNL`mv*$F*eUoyB$3sgv3xSu7vXK}jo!#2*%&2i zgE?O(ZdxJ%9sm)!2`x*NAmS`5D-pI1J4%>b!I(%E8qL}=rW$X$H~5ej$x(8{9a?Jv2Yw@url^0w{5_zXq#ytEUy<+jM`_=G_2 z7sB3ZNxv0e{&0b}=;?dj>7?E3u><<(b7*kJSWMXe`n#A#z{gwAc&s@ljvZ0Qw!-U} z&#`OB1O^f?;n)1UUS2*}866Y6_Lg*~Ab@!BMpEd}?V+qvTEEMszAU}#kTRZxrDAU; z0Tcta2!$9T-SISwuvHIThlTVX^Qv1V)*Bi#9T%jt=JIV>J z`bD_89D|R}&-U~{$MPi$cD0bs-lRPSo||^Az|L4?K8pl$BL2hu8wZUDnNRpTW#IBo zG7N-D=nCOM&#Ge4yy2XPEy}I~qMWiFnrDjW07@mIKmJYCN)f6=g*u;IA+R%5AmRND zbxx=YktruI0n!(|jTJs0E$gI3=e5bQNpp`ZQ~*fCZuK?T6-kK*${69AbwZoJl4HaH z@8r|Id@;3s0`_5Q4m_N49Y4Cdy;iGbz)Q(&zO^{`-j6ms$nqC|zSzlRj)d?W_c|t={=6BOiA1( z-`5N2j#ge>7AFBy(^Md0BIhpDxmh4#GbUU=30yZhHgR9#n6%&qk4iyv#}9H{LF+PT z{VYXsXiY4E)GJrefFLm}BO_UDo6S4$s9>_-QXy23G1u2aNBU@Z{0R183ZK9AmMgoF z-~1QLvfqqjzsk*J=b?y5^CY&cNh@V<_DYo}V6u2e;ZJH$QQ7%}wm;bG20X5+adDhX3(?r(9#j7p-VTyzCiL(chx}9<)`cgmhbmq z7xmdfc4DJD(Sm5j16uGki;R~7aaHugj3IB{8d&o8fnR&<*`AZP!hS?z{v&suW~beo zQIhtMI29(>41kj#MWDhwBoZe!ZwohqBaX!&A?{{4o;13d;04hnPEAS=l|dzIl|?rn z7qGZAn;EORy*3vcBqdomal5BZ-~@gZ#q*=W_wTVs#WgRSaQb-WjxR5Fo!q711s~z~ zlTIm;YA*tv=>cJB3Ccvl&(EOT{KDw_F8X2Uq&+};g?Ka%+w@Rp&64K|D_D%8wk^It z5A7m}gExXRZKyOjcq}TmAExhFEJD&b96Cp1$_LkE8=FA=ZwvuuYKz#h`o^F{FRf+= z-hWJ@VKdlq@bYgVKz&I`r9O=LA{Y_Aw*#fh#&REQ-zZj>;}6n$=he^QwGTGBsin4= zml$9dO6T-Ke;H@9P!fXg(F~~Kktc6k{ozO90PQv6>HpNC6R8ToHLrCr#bQ&ft(!zT zbq=z$C4vj`Ym^!H69mI383z$?RrFOIhVB5120TB1uGHL=T~8+U$eeHK%Hn9GQ0#NV z)7{5^0d|2hogI0T=|Pi%xG=(BA?L)aW zN1{n5Y`5a1VcY0``wMc4i&X&Tx82VJv+)K{d(1gb*YIUeL4ExE+i~i zrX-hfh(;;J=Ch9+aM1T_w~J!!s#lLY^Cj2?TDIXQZ$V$&RcQ4dBeZ^*cocn4))=KM zVrXC!8YsUi1?7L(apbPu>j3RJH3eSJx@%jCUh!k7-Mt+ZvXv;pI#5-j?ZL}VF1y() zSD%%Y@{N;yr3Ya3nkn=Am%h9{7 zptW^5D*Nw5%a5uCr-gD)P$0E}uV~>0BVMKUU{I4cZ9nmhecD5Qfc8!Le;dZ`l`Gx- Qg8%>k07*qoM6N<$f~qr2cmMzZ literal 0 HcmV?d00001 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 85a13e3f20..41142c964c 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -110,9 +110,13 @@ Nomination Pools Locked details Protect yourself from losing access to your funds + Stake and farm to earn yield\non Demeter Protocol + Demeter Protocol Buy XOR Buy or sell XOR token with\nEuro cash Buy XOR token + Invest your funds in Liquidity\npools and receive rewards + Liquidity pools Warning: By clicking connect, you allow this dapp to view your public address. This is an important security step to protect your data from potential phishing risks. Connected to %s Connected to @@ -308,6 +312,8 @@ Select Network Selected Share + Show details + Show more Sign Skip Skip process diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index 2e3471fa99..d04aa3afe6 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -1,5 +1,8 @@ package jp.co.soramitsu.polkaswap.impl.presentation.swap_tokens +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -12,6 +15,8 @@ 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.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -20,6 +25,7 @@ import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -34,6 +40,9 @@ import java.math.BigDecimal import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.AmountInput import jp.co.soramitsu.common.compose.component.AmountInputViewState +import jp.co.soramitsu.common.compose.component.BannerDemeter +import jp.co.soramitsu.common.compose.component.BannerLiquidityPools +import jp.co.soramitsu.common.compose.component.BannerPageIndicator import jp.co.soramitsu.common.compose.component.FeeInfo import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.compose.component.FullScreenLoading @@ -57,8 +66,7 @@ import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.feature_polkaswap_impl.R import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsViewState -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import kotlinx.coroutines.delay data class SwapTokensContentViewState( val fromAmountInputViewState: AmountInputViewState, @@ -74,8 +82,8 @@ data class SwapTokensContentViewState( fun default(resourceManager: ResourceManager): SwapTokensContentViewState { return SwapTokensContentViewState( - fromAmountInputViewState = AmountInputViewState.default(resourceManager, R.string.common_available_format), - toAmountInputViewState = AmountInputViewState.default(resourceManager), + fromAmountInputViewState = AmountInputViewState.defaultObj.copy(totalBalance = resourceManager.getString(R.string.common_available_format, "0")), + toAmountInputViewState = AmountInputViewState.defaultObj.copy(totalBalance = resourceManager.getString(R.string.common_balance_format, "0")), selectedMarket = Market.SMART, swapDetailsViewState = null, isLoading = false, @@ -117,7 +125,7 @@ interface SwapTokensCallbacks { fun onDisclaimerClick() - fun onPoolsClick(chainId: ChainId) + fun onPoolsClick() } @Composable @@ -254,15 +262,9 @@ fun SwapTokensContent( MarginVertical(margin = 16.dp) } - AccentButton( - modifier = Modifier - .height(48.dp) - .fillMaxWidth() - .padding(horizontal = 16.dp), - text = "POOLS", - onClick = { runCallback { callbacks.onPoolsClick(soraMainChainId) } } - ) - MarginVertical(margin = 8.dp) + if (state.fromAmountInputViewState.tokenName == null && state.toAmountInputViewState.tokenName == null) { + Banners(callbacks) + } AccentButton( modifier = Modifier @@ -277,7 +279,7 @@ fun SwapTokensContent( if (showQuickInput) { QuickInput( - values = QuickAmountInput.values(), + values = QuickAmountInput.entries.toTypedArray(), onQuickAmountInput = { keyboardController?.hide() callbacks.onQuickAmountInput(it) @@ -400,6 +402,71 @@ private fun MarketLabel( } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun Banners( + callback: SwapTokensCallbacks, + autoPlay: Boolean = true +) { + val bannerLiquidityPools: @Composable (() -> Unit) = + { + BannerLiquidityPools( + onShowMoreClick = callback::onPoolsClick, + onCloseClick = {} + ) + } + + val bannerDemeter: @Composable (() -> Unit)? = if (true) { + { + BannerDemeter( + onShowMoreClick = {}, + onCloseClick = {} + ) + } + } else null + + val banners = listOfNotNull(bannerLiquidityPools, bannerDemeter) + val bannersCount = banners.size + val pagerState = rememberPagerState { bannersCount } + + if (bannersCount > 1) { + // Auto play + LaunchedEffect(key1 = autoPlay) { + if (autoPlay) { + while (true) { + delay(5000L) + with(pagerState) { + animateScrollToPage( + page = (currentPage + 1) % bannersCount, + animationSpec = tween( + durationMillis = 500, + easing = FastOutSlowInEasing + ) + ) + } + } + } + } + } + HorizontalPager( + modifier = Modifier.fillMaxWidth(), + state = pagerState, + pageSpacing = 8.dp, + pageContent = { page -> + Box(modifier = Modifier.padding(horizontal = 16.dp)) { + banners[page].invoke() + } + } + ) + MarginVertical(margin = 8.dp) + + if (bannersCount > 1) { + BannerPageIndicator(bannersCount, pagerState) + MarginVertical(margin = 8.dp) + } + MarginVertical(margin = 8.dp) +} + @Preview @Composable fun SwapTokensContentPreview() { @@ -436,7 +503,7 @@ fun SwapTokensContentPreview() { override fun networkFeeTooltipClick() {} override fun onQuickAmountInput(value: Double) {} override fun onDisclaimerClick() {} - override fun onPoolsClick(chainId: ChainId) {} + override fun onPoolsClick() {} } SwapTokensContent( diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt index b50a48638a..1ebcf2b554 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt @@ -101,13 +101,14 @@ class SwapTokensViewModel @Inject constructor( savedStateHandle.get(SwapTokensFragment.KEY_SELECTED_CHAIN_ID) private val fromAmountInputViewState = MutableStateFlow( - AmountInputViewState.default( - resourceManager, - R.string.common_available_format + AmountInputViewState.defaultObj.copy( + totalBalance = resourceManager.getString(R.string.common_available_format, "0") ) ) - private val toAmountInputViewState = - MutableStateFlow(AmountInputViewState.default(resourceManager)) + private val toAmountInputViewState = MutableStateFlow( + AmountInputViewState.defaultObj.copy( + totalBalance = resourceManager.getString(R.string.common_balance_format, "0")) + ) private var selectedMarket = MutableStateFlow(Market.SMART) private var slippageTolerance = MutableStateFlow(0.5) @@ -727,7 +728,7 @@ class SwapTokensViewModel @Inject constructor( isSoftKeyboardOpenFlow.value = isOpen } - override fun onPoolsClick(chainId: ChainId) { - polkaswapRouter.openPools(chainId) + override fun onPoolsClick() { + polkaswapRouter.openPools(polkaswapInteractor.polkaswapChainId) } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt index a1125654ba..f8db508fb7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt @@ -36,6 +36,7 @@ import jp.co.soramitsu.common.compose.component.AssetBalance import jp.co.soramitsu.common.compose.component.AssetBalanceViewState import jp.co.soramitsu.common.compose.component.BannerBackup import jp.co.soramitsu.common.compose.component.BannerBuyXor +import jp.co.soramitsu.common.compose.component.BannerPageIndicator import jp.co.soramitsu.common.compose.component.ChangeBalanceViewState import jp.co.soramitsu.common.compose.component.GrayButton import jp.co.soramitsu.common.compose.component.MarginVertical @@ -44,8 +45,6 @@ import jp.co.soramitsu.common.compose.component.MultiToggleButtonState import jp.co.soramitsu.common.compose.component.NetworkIssuesBadge import jp.co.soramitsu.common.compose.component.SwipeState import jp.co.soramitsu.common.compose.theme.FearlessAppTheme -import jp.co.soramitsu.common.compose.theme.white16 -import jp.co.soramitsu.common.compose.theme.white50 import jp.co.soramitsu.common.compose.viewstate.AssetListItemViewState import jp.co.soramitsu.common.utils.rememberForeverLazyListState import jp.co.soramitsu.feature_wallet_impl.R @@ -209,31 +208,6 @@ private fun Banners(data: WalletState, callback: WalletScreenInterface) { } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun BannerPageIndicator( - bannersCount: Int, - pagerState: PagerState -) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - repeat(bannersCount) { iteration -> - val color = if (pagerState.currentPage == iteration) white16 else white50 - Box( - modifier = Modifier - .padding(horizontal = 3.dp) - .clip(CircleShape) - .background(color) - .size(8.dp) - ) - } - } -} - @Composable fun WalletScreenWithRefresh( data: WalletState, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/nft/list/NftList.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/nft/list/NftList.kt index ab738ba3f8..430c3626d7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/nft/list/NftList.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/nft/list/NftList.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.wallet.impl.presentation.balance.nft.list -import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -24,7 +23,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -53,7 +51,6 @@ import jp.co.soramitsu.common.compose.theme.black50 import jp.co.soramitsu.common.compose.theme.warningOrange import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white50 -import jp.co.soramitsu.common.utils.castOrNull import jp.co.soramitsu.common.utils.clickableSingle import jp.co.soramitsu.common.compose.utils.PageScrollingCallback import jp.co.soramitsu.common.compose.utils.nestedScrollConnectionForPageScrolling From 9fa8a7ffa448c021dd6c669420c8e3ac90284bb6 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Sat, 29 Jun 2024 11:23:49 +0500 Subject: [PATCH 27/84] modules init --- feature-liquiditypools-api/.gitignore | 1 + feature-liquiditypools-api/build.gradle.kts | 38 ++++++++++ .../src/main/AndroidManifest.xml | 4 + feature-liquiditypools-impl/.gitignore | 1 + feature-liquiditypools-impl/build.gradle.kts | 75 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 9 +++ settings.gradle | 2 + 7 files changed, 130 insertions(+) create mode 100644 feature-liquiditypools-api/.gitignore create mode 100644 feature-liquiditypools-api/build.gradle.kts create mode 100644 feature-liquiditypools-api/src/main/AndroidManifest.xml create mode 100644 feature-liquiditypools-impl/.gitignore create mode 100644 feature-liquiditypools-impl/build.gradle.kts create mode 100644 feature-liquiditypools-impl/src/main/AndroidManifest.xml diff --git a/feature-liquiditypools-api/.gitignore b/feature-liquiditypools-api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/feature-liquiditypools-api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature-liquiditypools-api/build.gradle.kts b/feature-liquiditypools-api/build.gradle.kts new file mode 100644 index 0000000000..b1b833b4de --- /dev/null +++ b/feature-liquiditypools-api/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + id("com.android.library") + id("kotlin-android") + id("kotlin-parcelize") +} + +android { + namespace = "jp.co.soramitsu.feature_liquiditypools_api" + compileSdk = rootProject.ext["compileSdkVersion"] as Int + + defaultConfig { + minSdk = rootProject.ext["minSdkVersion"] as Int + targetSdk = rootProject.ext["targetSdkVersion"] as Int + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(projects.androidFoundation) + implementation(projects.featureAccountApi) + implementation(projects.featureWalletImpl) + implementation(projects.common) + implementation(projects.runtime) + implementation("javax.inject:javax.inject:1") + + implementation(libs.bundles.coroutines) + implementation(libs.sharedFeaturesCoreDep) { + exclude(module = "android-foundation") + } +} \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/AndroidManifest.xml b/feature-liquiditypools-api/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a5918e68ab --- /dev/null +++ b/feature-liquiditypools-api/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature-liquiditypools-impl/.gitignore b/feature-liquiditypools-impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/feature-liquiditypools-impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature-liquiditypools-impl/build.gradle.kts b/feature-liquiditypools-impl/build.gradle.kts new file mode 100644 index 0000000000..3d52c9133f --- /dev/null +++ b/feature-liquiditypools-impl/build.gradle.kts @@ -0,0 +1,75 @@ +import groovy.lang.Closure + +plugins { + id("com.android.library") + id("dagger.hilt.android.plugin") + id("kotlin-android") + id("kotlin-kapt") + id("kotlin-parcelize") +} +android { + namespace = "jp.co.soramitsu.feature_liquiditypools_impl" + compileSdk = rootProject.ext["compileSdkVersion"] as Int + + defaultConfig { + minSdk = rootProject.ext["minSdkVersion"] as Int + targetSdk = rootProject.ext["targetSdkVersion"] as Int + } + + buildFeatures { + compose = true + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + composeOptions { + kotlinCompilerExtensionVersion = rootProject.ext["composeCompilerVersion"] as String + } + + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(projects.androidFoundation) + implementation(projects.common) + implementation(projects.runtime) + +// implementation(project(":feature-wallet-api")) +// + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) + implementation(libs.bundles.compose) + implementation(libs.fragmentKtx) + implementation(libs.material) +// implementation(libs.sharedFeaturesCoreDep) { +// exclude(module = "android-foundation") +// } +// implementation(libs.retrofit) +// implementation(libs.gson) +// implementation(libs.web3jDep) { +// exclude(group = "org.java-websocket", module = "Java-WebSocket") +// } +// +// implementation(libs.converter.gson) +// implementation(libs.converter.scalars) +// + implementation(libs.navigation.compose) + implementation(libs.sora.ui) +// +// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.7.3") +// + implementation(projects.featureLiquiditypoolsApi) +// implementation(projects.featureNftApi) + implementation(projects.featureAccountApi) + implementation(projects.featureWalletApi) + implementation(projects.featureWalletImpl) +//// implementation(projects.coreDb) +// implementation(projects.coreApi) +// implementation(kotlin("script-runtime")) +} diff --git a/feature-liquiditypools-impl/src/main/AndroidManifest.xml b/feature-liquiditypools-impl/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..afa76499b6 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9c52657797..59423edbc4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,3 +28,5 @@ include ':feature-walletconnect-api' include ':feature-walletconnect-impl' include ':feature-nft-api' include ':feature-nft-impl' +include ':feature-liquiditypools-api' +include ':feature-liquiditypools-impl' From afa3a95fece7de93455d34195c1ebd900d11b834 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 2 Jul 2024 12:59:27 +0500 Subject: [PATCH 28/84] all pools --- app/build.gradle | 3 + .../soramitsu/app/di/app/NavigationModule.kt | 5 + .../app/root/navigation/Navigator.kt | 10 +- .../main/res/navigation/main_nav_graph.xml | 5 + .../soramitsu/common/utils/FearlessLibExt.kt | 2 + feature-liquiditypools-api/build.gradle.kts | 2 + .../domain/interfaces/PoolsInteractor.kt | 7 + .../navigation/LiquidityPoolsRouter.kt | 6 + feature-liquiditypools-impl/build.gradle.kts | 1 + .../liquiditypools/impl/di/PoolsModule.kt | 31 +++ .../impl/domain/PoolsInteractorImpl.kt | 14 ++ .../presentation/allpools/AllPoolsFragment.kt | 35 +++ .../presentation/allpools/AllPoolsScreen.kt | 217 ++++++++++++++++++ .../allpools/AllPoolsViewModel.kt | 87 +++++++ .../allpools/BasicPoolListItem.kt | 166 ++++++++++++++ .../polkaswap/api/data/PolkaswapRepository.kt | 3 + .../api/domain/models/BasicPoolData.kt | 57 +++++ feature-polkaswap-impl/build.gradle.kts | 7 +- .../impl/data/PolkaswapRepositoryImpl.kt | 191 ++++++++++++++- .../impl/di/PolkaswapFeatureBindModule.kt | 13 +- .../swap_tokens/SwapTokensContent.kt | 2 +- gradle/libs.versions.toml | 2 +- .../runtime/multiNetwork/ChainRegistry.kt | 8 +- 23 files changed, 854 insertions(+), 20 deletions(-) create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsRouter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt create mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt diff --git a/app/build.gradle b/app/build.gradle index 9ef09b26c7..324435c406 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -176,6 +176,9 @@ dependencies { implementation project(':feature-nft-api') implementation project(':feature-nft-impl') + implementation project(':feature-liquiditypools-api') + implementation project(':feature-liquiditypools-impl') + implementation libs.kotlin.stdlib.jdk7 implementation libs.appcompat diff --git a/app/src/main/java/jp/co/soramitsu/app/di/app/NavigationModule.kt b/app/src/main/java/jp/co/soramitsu/app/di/app/NavigationModule.kt index 9f77c855e6..f6f360f8c5 100644 --- a/app/src/main/java/jp/co/soramitsu/app/di/app/NavigationModule.kt +++ b/app/src/main/java/jp/co/soramitsu/app/di/app/NavigationModule.kt @@ -9,6 +9,7 @@ import javax.inject.Singleton import jp.co.soramitsu.account.impl.presentation.AccountRouter import jp.co.soramitsu.app.root.navigation.Navigator import jp.co.soramitsu.crowdloan.impl.presentation.CrowdloanRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.nft.navigation.NFTRouter import jp.co.soramitsu.onboarding.impl.OnboardingRouter import jp.co.soramitsu.polkaswap.api.presentation.PolkaswapRouter @@ -69,4 +70,8 @@ class NavigationModule { @Singleton @Provides fun provideNFTRouter(navigator: Navigator): NFTRouter = navigator + + @Singleton + @Provides + fun provideLiquidityPoolsRouter(navigator: Navigator): LiquidityPoolsRouter = navigator } diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 279d1a0032..02077d6986 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -73,12 +73,12 @@ import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.CustomContr import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.model.CustomContributePayload import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.CrowdloanContributeFragment import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.parcel.ContributePayload +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.nft.impl.presentation.NFTFlowFragment import jp.co.soramitsu.nft.navigation.NFTRouter import jp.co.soramitsu.onboarding.impl.OnboardingRouter import jp.co.soramitsu.onboarding.impl.welcome.WelcomeFragment import jp.co.soramitsu.onboarding.impl.welcome.select_import_mode.SelectImportModeDialog -import jp.co.soramitsu.polkaswap.PoolsRoutes import jp.co.soramitsu.polkaswap.api.presentation.PolkaswapRouter import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsParcelModel import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsViewState @@ -178,6 +178,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -202,7 +203,8 @@ class Navigator : SuccessRouter, SoraCardRouter, WalletConnectRouter, - NFTRouter + NFTRouter, + LiquidityPoolsRouter { private var navController: NavController? = null @@ -1519,8 +1521,6 @@ class Navigator : } override fun openPools(chainId: ChainId) { - navController?.navigate( - "${PoolsRoutes.POOLS_EXPLORE_SCREEN}/$chainId" - ) + navController?.navigate(R.id.allPoolsFragment) } } diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index cc762fd355..50828a50ec 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -934,6 +934,11 @@ app:popExitAnim="?android:attr/fragmentCloseExitAnimation" /> + + +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsRouter.kt new file mode 100644 index 0000000000..bda191f2ab --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsRouter.kt @@ -0,0 +1,6 @@ +package jp.co.soramitsu.liquiditypools.navigation + +interface LiquidityPoolsRouter { + + fun back() +} diff --git a/feature-liquiditypools-impl/build.gradle.kts b/feature-liquiditypools-impl/build.gradle.kts index 3d52c9133f..20f5b247d7 100644 --- a/feature-liquiditypools-impl/build.gradle.kts +++ b/feature-liquiditypools-impl/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(projects.androidFoundation) implementation(projects.common) implementation(projects.runtime) + implementation(projects.featurePolkaswapApi) // implementation(project(":feature-wallet-api")) // diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt new file mode 100644 index 0000000000..aedb1275ad --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -0,0 +1,31 @@ +package jp.co.soramitsu.liquiditypools.impl.di + +import androidx.navigation.Navigator +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter +import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.wallet.impl.presentation.WalletRouter + +@Module +@InstallIn(SingletonComponent::class) +class PoolsModule { + + @Provides + @Singleton + fun providesPoolIteractor( + polkaswapRepository: PolkaswapRepository + ): PoolsInteractor = + PoolsInteractorImpl(polkaswapRepository) + +// @Provides +// @Singleton +// fun provideLiquidityPoolsRouter(walletRouter: WalletRouter): InternalNFTRouter = InternalNFTRouterImpl( +// walletRouter = walletRouter +// ) +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt new file mode 100644 index 0000000000..38849d3631 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -0,0 +1,14 @@ +package jp.co.soramitsu.liquiditypools.impl.domain + +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository + +class PoolsInteractorImpl( + private val polkaswapRepository: PolkaswapRepository, +) : PoolsInteractor { + + override suspend fun getBasicPools(): List { + return polkaswapRepository.getBasicPools() + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt new file mode 100644 index 0000000000..fede6fd954 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt @@ -0,0 +1,35 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import android.widget.FrameLayout +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.fragment.app.viewModels +import com.google.android.material.bottomsheet.BottomSheetBehavior +import dagger.hilt.android.AndroidEntryPoint +import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment +import jp.co.soramitsu.common.compose.component.BottomSheetScreen + +@AndroidEntryPoint +class AllPoolsFragment : BaseComposeBottomSheetDialogFragment() { + + override val viewModel: AllPoolsViewModel by viewModels() + + @Composable + override fun Content(padding: PaddingValues) { + val state by viewModel.state.collectAsState() + BottomSheetScreen { + AllPoolsScreen( + state, + callback = viewModel, + ) + } + } + + override fun setupBehavior(behavior: BottomSheetBehavior) { + behavior.state = BottomSheetBehavior.STATE_EXPANDED + behavior.isHideable = true + behavior.skipCollapsed = true + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt new file mode 100644 index 0000000000..bd5465980e --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -0,0 +1,217 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import android.graphics.Paint.Align +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.Row +import androidx.compose.foundation.layout.Spacer +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.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +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.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.R.drawable +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.Image +import jp.co.soramitsu.common.compose.component.NavigationIconButton +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.common.compose.theme.red +import jp.co.soramitsu.common.compose.theme.transparent +import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.compose.theme.white08 +import jp.co.soramitsu.common.utils.clickableWithNoIndication +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.ui_core.resources.Dimens +import jp.co.soramitsu.ui_core.theme.customColors + +data class AllPoolsState( + val pools: List = listOf() +) + +interface AllPoolsScreenInterface { + fun onPoolClicked(pair: StringPair) + fun onNavigationClick() + fun onCloseClick() + fun onMoreClick() +} + +@Composable +fun AllPoolsScreen( + state: AllPoolsState, + callback: AllPoolsScreenInterface +) { + Column( + modifier = Modifier.fillMaxSize(), + ) { + Toolbar(callback) + + val listState = rememberLazyListState() + + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) + ) { + LazyColumn( + state = listState, + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier + .wrapContentHeight() + ) { + item { + PoolGroupHeader(callback) + } + items(state.pools) { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } + } + } + } +} + +@Composable +private fun PoolGroupHeader(callback: AllPoolsScreenInterface) { + Box(modifier = Modifier.wrapContentHeight()) { + Row( + modifier = Modifier + .padding(vertical = Dimens.x1_5, horizontal = Dimens.x1_5) + .wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.wrapContentHeight(), + color = white, + style = MaterialTheme.customTypography.header5, + text = "Your pools", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .background( + color = white08, + shape = CircleShape, + ) + .padding(all = Dimens.x1) + ) { + Text( + text = "MORE", + modifier = Modifier + .align(Alignment.CenterVertically), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.customTypography.capsTitle2, + color = white, + ) + Image( + res = R.drawable.ic_chevron_right, + modifier = Modifier + .padding(start = 8.dp) + .align(Alignment.CenterVertically) + .clickableWithNoIndication(callback::onMoreClick) + ) + } + } + Box( + modifier = Modifier + .height(1.dp) + .padding(horizontal = Dimens.x1_5) + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background(white08) + ) + } +} + +@Composable +private fun Toolbar(callback: AllPoolsScreenInterface) { + Row( + modifier = Modifier + .wrapContentHeight() + .padding(bottom = 12.dp) + ) { + NavigationIconButton( + modifier = Modifier.padding(start = 16.dp), + onNavigationClick = callback::onNavigationClick + ) + + Image( + modifier = Modifier + .padding(start = 8.dp) + .align(Alignment.Top) + .size( + width = 100.dp, + height = 28.dp + ), + res = R.drawable.logo_polkaswap_big, + contentDescription = null + ) + Spacer(modifier = Modifier.weight(1f)) + NavigationIconButton( + modifier = Modifier + .align(Alignment.Top) + .padding(end = 16.dp), + navigationIconResId = drawable.ic_cross_32, + onNavigationClick = callback::onCloseClick + ) + } +} + +@Preview +@Composable +private fun PreviewAllPoolsInternal() { +val itemState = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "XOR-VAL", + text2 = "123.4M", + text3 = "1234.3%", + text4 = "Earn SWAP", + ) + + val items = listOf( + itemState, + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", text3 = "TEXT3", text4 = "TEXT4"), + itemState.copy(text1 = "text1", text2 = "text2", text3 = "text3", text4 = "text4"), + ) + Column { + BottomSheetScreen { + AllPoolsScreen( + state = AllPoolsState( + pools = items, + ), + callback = object : AllPoolsScreenInterface { + override fun onPoolClicked(pair: StringPair) {} + override fun onNavigationClick() {} + override fun onCloseClick() {} + override fun onMoreClick() {} + }, + ) + } + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt new file mode 100644 index 0000000000..02e21f0fe3 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt @@ -0,0 +1,87 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import dagger.hilt.android.lifecycle.HiltViewModel +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.base.BaseViewModel +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.mapList +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +@HiltViewModel +class AllPoolsViewModel @Inject constructor( + private val poolsInteractor: PoolsInteractor, + private val poolsRouter: LiquidityPoolsRouter +) : BaseViewModel(), AllPoolsScreenInterface { + + private val _state = MutableStateFlow(AllPoolsState()) + val pools = flowOf { + poolsInteractor.getBasicPools().sortedWith { o1, o2 -> + compareNullDesc(o1.tvl, o2.tvl) + } + }.map { + it.map(BasicPoolData::toListItemState) + }.share() + + val state = _state.asStateFlow() + + init { + subscribeScreenState() + } + + private fun subscribeScreenState() { + pools.onEach { + _state.value = _state.value.copy(pools = it) + }.launchIn(this) + } + + override fun onPoolClicked(pair: StringPair) { + println("!!! CLIKED ON PoolPair: $pair") + } + + override fun onNavigationClick() { + println("!!! CLIKED onNavigationClick") + exitFlow() + } + override fun onCloseClick() { + println("!!! CLIKED onCloseClick") + exitFlow() + } + + override fun onMoreClick() { + println("!!! CLIKED onMoreClick") + + + } + + fun exitFlow() { + poolsRouter.back() + } +} + +private fun BasicPoolData.toListItemState(): BasicPoolListItemState { + val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) + ?.multiply(this.baseReserves) + + return BasicPoolListItemState( + ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.id.orEmpty()), // todo + token1Icon = this.baseToken.token.configuration.iconUrl, + token2Icon = this.targetToken?.iconUrl.orEmpty(), + text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.symbol}", + text2 = tvl?.formatFiat().orEmpty(), + text3 = this.sbapy?.let { + "%s%%".format(it.toBigDecimal().formatCrypto()) + }.orEmpty(), + ) +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt new file mode 100644 index 0000000000..05ccca96db --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -0,0 +1,166 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.compose.theme.FearlessAppTheme +import jp.co.soramitsu.common.compose.theme.colorAccentDark +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.common.compose.theme.transparent +import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.compose.theme.white50 +import jp.co.soramitsu.ui_core.component.button.properties.Size +import jp.co.soramitsu.ui_core.component.icons.TokenIcon + +data class BasicPoolListItemState( + val ids: StringPair, + val token1Icon: String, + val token2Icon: String, + val text1: String, + val text2: String, + val text3: String, + val text4: String? = null, +) + +@Composable +fun BasicPoolListItem( + modifier: Modifier = Modifier, + state: BasicPoolListItemState, + onPoolClick: ((StringPair) -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .clickable { onPoolClick?.invoke(state.ids) }, + verticalAlignment = Alignment.CenterVertically, + ) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + ConstraintLayout( + modifier = Modifier + .wrapContentWidth() + .padding(start = 12.dp) + ) { + val (token1, token2) = createRefs() + TokenIcon( + modifier = Modifier + .constrainAs(token1) { + top.linkTo(parent.top, 2.dp) + start.linkTo(parent.start) + bottom.linkTo(parent.bottom, 11.dp) + }, + uri = state.token1Icon, + size = Size.ExtraSmall, + ) + TokenIcon( + modifier = Modifier + .constrainAs(token2) { + top.linkTo(parent.top, 11.dp) + start.linkTo(token1.start, margin = 16.dp) + bottom.linkTo(parent.bottom, 2.dp) + }, + uri = state.token2Icon, + size = Size.ExtraSmall, + ) + } + } + Column( + modifier = Modifier + .wrapContentHeight() + .weight(1f) + .padding(start = 8.dp, end = 12.dp), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.wrapContentHeight(), + color = white, + style = MaterialTheme.customTypography.header6, + text = state.text1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Text( + modifier = Modifier + .wrapContentHeight() + .weight(1f), + color = colorAccentDark, + style = MaterialTheme.customTypography.header6, + text = "%s %s".format(state.text3, "APY"), //stringResource(id = R.string.polkaswap_apy)), + maxLines = 1, + textAlign = TextAlign.End + ) + + } + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.wrapContentHeight(), + color = white50, + style = MaterialTheme.customTypography.body2, + text = state.text4.orEmpty(), + maxLines = 1, + ) + + Text( + modifier = Modifier.wrapContentHeight(), + color = white50, + style = MaterialTheme.customTypography.body2, + text = state.text2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } +} + + +@Preview +@Composable +private fun PreviewBasicPoolListItem() { + FearlessAppTheme { + BasicPoolListItem( + modifier = Modifier.background(transparent), + state = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "XOR-VAL", + text2 = "123.4M", + text3 = "1234.3%", + text4 = "Earn SWAP", + ) + ) + } +} diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index 13222967a3..cdd79d7c9e 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -6,6 +6,7 @@ import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigInteger +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData interface PolkaswapRepository { suspend fun getAvailableDexes(chainId: ChainId): List @@ -53,4 +54,6 @@ interface PolkaswapRepository { markets: List, desired: WithDesired ): Result + + suspend fun getBasicPools(): List } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt new file mode 100644 index 0000000000..38887f0acc --- /dev/null +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt @@ -0,0 +1,57 @@ +package jp.co.soramitsu.polkaswap.api.domain.models + +import android.os.Parcelable +import java.math.BigDecimal +import jp.co.soramitsu.core.models.Asset +import kotlinx.parcelize.Parcelize + +data class BasicPoolData( +// val baseToken: Token, +// val targetToken: Token, + val baseToken: jp.co.soramitsu.wallet.impl.domain.model.Asset, + val targetToken: Asset?, + val baseReserves: BigDecimal, + val targetReserves: BigDecimal, + val totalIssuance: BigDecimal, + val reserveAccount: String, + val sbapy: Double?, +) { + val fiatSymbol = "baseToken.fiatSymbol" + val tvl: BigDecimal? + get() = baseToken.token.fiatRate?.times(BigDecimal(2))?.multiply(baseReserves) +} + +@Parcelize +data class Token( + val id: String, + val name: String, + val symbol: String, + val precision: Int, + val isHidable: Boolean, + val iconFile: String?, + val fiatPrice: Double?, + val fiatPriceChange: Double?, + val fiatSymbol: String?, +) : Parcelable { + +// fun printBalance( +// balance: BigDecimal, +// nf: NumbersFormatter, +// precision: Int = this.precision +// ): String = +// String.format("%s %s", nf.formatBigDecimal(balance, precision), symbol) + +// fun printBalanceWithConstrainedLength( +// balance: BigDecimal, +// nf: NumbersFormatter, +// length: Int = 8 +// ): String { +// val integerLength = balance.toBigInteger().toString().length +// var newPrecision = length - integerLength +// if (newPrecision <= 0) { +// newPrecision = 1 +// } +// +// return printBalance(balance, nf, newPrecision) +// } +} diff --git a/feature-polkaswap-impl/build.gradle.kts b/feature-polkaswap-impl/build.gradle.kts index b1797f9e56..dc0800219c 100644 --- a/feature-polkaswap-impl/build.gradle.kts +++ b/feature-polkaswap-impl/build.gradle.kts @@ -46,10 +46,11 @@ dependencies { exclude(group = "jp.co.soramitsu.xnetworking", module = "basic") } - api(libs.sharedFeaturesPoolsDep) { - exclude(module = "android-foundation") - } +// api(libs.sharedFeaturesPoolsDep) { +// exclude(module = "android-foundation") +// } + implementation(projects.androidFoundation) implementation(projects.coreDb) implementation(projects.common) implementation(projects.runtime) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 48c9e70271..6a47ea807b 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -1,10 +1,16 @@ package jp.co.soramitsu.polkaswap.impl.data +import java.math.BigDecimal import java.math.BigInteger import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.accountId +import jp.co.soramitsu.androidfoundation.format.addHexPrefix +import jp.co.soramitsu.androidfoundation.format.mapBalance +import jp.co.soramitsu.androidfoundation.format.safeCast import jp.co.soramitsu.common.data.network.config.PolkaswapRemoteConfig import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher +import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.dexManager import jp.co.soramitsu.common.utils.poolTBC import jp.co.soramitsu.common.utils.poolXYK @@ -13,6 +19,7 @@ import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.api.models.backStrings @@ -20,20 +27,31 @@ import jp.co.soramitsu.polkaswap.api.models.toFilters import jp.co.soramitsu.polkaswap.api.models.toMarkets import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.bindings.bindDexInfos import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.swap +import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct +import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex +import jp.co.soramitsu.shared_utils.runtime.metadata.module import jp.co.soramitsu.shared_utils.runtime.metadata.storage import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey +import jp.co.soramitsu.shared_utils.scale.Schema +import jp.co.soramitsu.shared_utils.scale.sizedByteArray +import jp.co.soramitsu.shared_utils.scale.uint128 import jp.co.soramitsu.shared_utils.wsrpc.exception.RpcException import jp.co.soramitsu.shared_utils.wsrpc.executeAsync import jp.co.soramitsu.shared_utils.wsrpc.mappers.nonNull import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojoList +import jp.co.soramitsu.shared_utils.wsrpc.mappers.scale import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow @@ -44,7 +62,9 @@ class PolkaswapRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, private val chainRegistry: ChainRegistry, private val rpcCalls: RpcCalls, - private val accountRepository: AccountRepository + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository + ) : PolkaswapRepository { override suspend fun getAvailableDexes(chainId: ChainId): List { @@ -204,4 +224,173 @@ class PolkaswapRepositoryImpl @Inject constructor( listOf() } } + + fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } + fun String.mapAssetId() = this.fromHex().mapAssetId() + fun String.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) + ) + + fun RuntimeSnapshot.reservesKeyToken(baseTokenId: String): String = + this.metadata.module(Modules.POOL_XYK) + .storage("Reserves") + .storageKey( + this, + baseTokenId.mapCodeToken(), + ) + + suspend fun getStorageHex(storageKey: String): String? = + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo(), + ).result + + suspend fun getStateKeys(partialKey: String): List = + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = StateKeys(listOf(partialKey)), + mapper = pojoList(), + ).result ?: emptyList() + + class StateKeys(params: List) : RuntimeRequest("state_getKeys", params) + + fun ByteArray.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) + ) + + object PoolPropertiesResponse : Schema() { + val first by sizedByteArray(32) + val second by sizedByteArray(32) + } + + suspend fun getPoolReserveAccount( + baseTokenId: String, + tokenId: ByteArray + ): ByteArray? { + val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val storageKey = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Properties")?.storageKey( + runtimeOrNull, + baseTokenId.mapCodeToken(), + tokenId.mapCodeToken(), + ) + ?: return null + + return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(PoolPropertiesResponse), + ) + .result + ?.let { storage -> + storage[storage.schema.first] + } + } + + fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() + object TotalIssuance : Schema() { + val totalIssuance by uint128() + } + + suspend fun getPoolTotalIssuances( + reservesAccountId: ByteArray, + ): BigInteger? { + val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) + ?.storage("TotalIssuances") + ?.storageKey(runtimeOrNull, reservesAccountId) + ?: return null + return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(TotalIssuance), + ) + .result + ?.let { storage -> + storage[storage.schema.totalIssuance] + } + } + + protected fun getPoolStrategicBonusAPY( + reserveAccountOfPool: String, + ): Double? = null +// blockExplorerManager.getTempApy(reserveAccountOfPool) + + override suspend fun getBasicPools(): List { + println("!!! getBasicPools() start") + val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val storage = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Reserves") + println("!!! getBasicPools() storage = $storage") + val list = mutableListOf() + + val soraChain = chainRegistry.getChain(soraMainChainId) + val assets = soraChain.assets + + val wallet = accountRepository.getSelectedMetaAccount() + + val accountId = wallet.accountId(soraChain) + val soraAssets = soraChain.assets.mapNotNull { chainAsset -> + accountId?.let { + walletRepository.getAsset( + metaId = wallet.id, + accountId = accountId, + chainAsset = chainAsset, + minSupportedVersion = null + ) + } + } + + println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") + + soraAssets.forEach { asset -> + println("!!! getBasicPools() soraAssets.forEach { asset ${asset.token.configuration.symbol}") + + val currencyId = asset.token.configuration.currencyId + val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } + key?.let { + getStateKeys(it).forEach { storageKey -> + val targetToken = storageKey.assetIdFromKey() + getStorageHex(storageKey)?.let { storageHex -> + storage?.type?.value + ?.fromHex(runtimeOrNull, storageHex) + ?.safeCast>()?.let { reserves -> + + val reserveAccount = getPoolReserveAccount( + currencyId, + targetToken.fromHex() + ) + val total = reserveAccount?.let { + getPoolTotalIssuances(it) + }?.let { + mapBalance(it, asset.token.configuration.precision) + } + val targetAsset = assets.firstOrNull { it.currencyId == targetToken } + val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" + + val element = BasicPoolData( + asset, + targetAsset, + mapBalance(reserves[0], asset.token.configuration.precision), + mapBalance(reserves[1], asset.token.configuration.precision), + total ?: BigDecimal.ZERO, + reserveAccountAddress, + sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) + ) + + println("!!! getBasicPools() list.add(BasicPoolData: $element") + list.add( + element + ) + // todo remove + if (list.size > 10) return list + } + } + } + } + } + + println("!!! getBasicPools() return list.size = ${list.size}") + + return list + } } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 7a238c911b..98a113bb09 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -43,9 +43,18 @@ class PolkaswapFeatureModule { extrinsicService: ExtrinsicService, chainRegistry: ChainRegistry, rpcCalls: RpcCalls, - accountRepository: AccountRepository + accountRepository: AccountRepository, + walletRepository: WalletRepository ): PolkaswapRepository { - return PolkaswapRepositoryImpl(remoteConfigFetcher, remoteSource, extrinsicService, chainRegistry, rpcCalls, accountRepository) + return PolkaswapRepositoryImpl( + remoteConfigFetcher, + remoteSource, + extrinsicService, + chainRegistry, + rpcCalls, + accountRepository, + walletRepository + ) } @Provides diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index d04aa3afe6..c73268ecb2 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -416,7 +416,7 @@ private fun Banners( ) } - val bannerDemeter: @Composable (() -> Unit)? = if (true) { + val bannerDemeter: @Composable (() -> Unit)? = if (false) { { BannerDemeter( onShowMoreClick = {}, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d64bf066a..5a3a3216ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -170,7 +170,7 @@ detekt-cli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = sharedFeaturesCoreDep = { module = "jp.co.soramitsu.shared_features:core", version.ref = "sharedFeaturesVersion" } sharedFeaturesXcmDep = { module = "jp.co.soramitsu.shared_features:xcm", version.ref = "sharedFeaturesVersion" } sharedFeaturesBackupDep = { module = "jp.co.soramitsu.shared_features:backup", version.ref = "sharedFeaturesVersion" } -sharedFeaturesPoolsDep = { module = "jp.co.soramitsu.shared_features:polkaswap", version.ref = "sharedFeaturesVersion" } +#sharedFeaturesPoolsDep = { module = "jp.co.soramitsu.shared_features:polkaswap", version.ref = "sharedFeaturesVersion" } beacon-android-sdk = { module = "com.github.airgap-it:beacon-android-sdk", version.ref = "beaconVersion" } web3jDep = { module = "org.web3j:core", version.ref = "web3j" } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt index 1ce066788a..3fdf02bad6 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt @@ -9,7 +9,6 @@ import jp.co.soramitsu.common.utils.diffed import jp.co.soramitsu.common.utils.failure import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.core.models.ChainsMetaAccount import jp.co.soramitsu.core.models.IChain import jp.co.soramitsu.core.runtime.ChainConnection import jp.co.soramitsu.core.runtime.IChainRegistry @@ -39,7 +38,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -220,10 +218,6 @@ class ChainRegistry @Inject constructor( return getChain(chainId).assetsById[chainAssetId] } - override fun chainsAccountFlow(metaAccountId: Long): Flow { - TODO("Not yet implemented") - } - override suspend fun getChain(chainId: ChainId): Chain { return chainsRepository.getChain(chainId) } @@ -286,7 +280,7 @@ class ChainRegistry @Inject constructor( override fun getConnection(chainId: String) = connectionPool.getConnectionOrThrow(chainId) - suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) + override suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) @Deprecated( "Since we have ethereum chains, which don't have runtime, we must use the function with nullable return value", ReplaceWith("getRuntimeOrNull(chainId)") From 3279a78bb27535cb13545699304ad8ca77a4e6d9 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 9 Jul 2024 21:51:38 +0500 Subject: [PATCH 29/84] pool details wip --- .../domain/interfaces/PoolsInteractor.kt | 10 + .../navigation/InternalPoolsRouter.kt | 14 + .../navigation/LiquidityPoolsNavGraphRoute.kt | 58 +++ .../liquiditypools/navigation/NavAction.kt | 5 + .../liquiditypools/impl/di/PoolsModule.kt | 22 +- .../impl/domain/PoolsInteractorImpl.kt | 33 ++ .../navigation/InternalPoolsRouterImpl.kt | 38 ++ .../presentation/allpools/AllPoolsFragment.kt | 91 +++- .../presentation/allpools/AllPoolsScreen.kt | 100 +++- .../allpools/AllPoolsViewModel.kt | 139 +++++- .../pooldetails/PoolDetailsScreen.kt | 227 +++++++++ .../presentation/poollist/PoolListScreen.kt | 147 ++++++ .../polkaswap/api/data/PolkaswapRepository.kt | 14 + .../polkaswap/api/data/PoolDataDto.kt | 13 + .../api/domain/models/BasicPoolData.kt | 4 +- .../api/domain/models/UserPoolData.kt | 57 +++ .../impl/data/PolkaswapRepositoryImpl.kt | 443 ++++++++++++++++-- .../polkaswap/impl/util/PolkaswapFormulas.kt | 107 +++++ .../balance/list/BalanceListViewModel.kt | 4 +- 19 files changed, 1465 insertions(+), 61 deletions(-) create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt create mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt create mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt create mode 100644 feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 1d124ea2a3..d15b42542a 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -1,7 +1,17 @@ package jp.co.soramitsu.liquiditypools.domain.interfaces +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData interface PoolsInteractor { suspend fun getBasicPools(): List + + suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? + + suspend fun getUserPoolData( + address: String, + baseTokenId: String, + tokenId: ByteArray + ): PoolDataDto? } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt new file mode 100644 index 0000000000..8807f5f8ef --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -0,0 +1,14 @@ +package jp.co.soramitsu.liquiditypools.navigation + +import jp.co.soramitsu.androidfoundation.format.StringPair +import kotlinx.coroutines.flow.Flow + +interface InternalPoolsRouter { + fun createNavGraphRoutesFlow(): Flow + fun createNavGraphActionsFlow(): Flow + fun back() + + fun openDetailsPoolScreen(ids: StringPair) + + fun openPoolListScreen() +} \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt new file mode 100644 index 0000000000..125b947aa5 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -0,0 +1,58 @@ +package jp.co.soramitsu.liquiditypools.navigation + +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId + +sealed interface LiquidityPoolsNavGraphRoute { + + val routeName: String + + object Loading : LiquidityPoolsNavGraphRoute { + override val routeName: String = "Loading" + } + + class AllPoolsScreen( + val chainId: ChainId, + val contractAddress: String + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "AllPoolsScreen" + } + } + + class ListPoolsScreen( +// val token: NFT + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "ListPoolsScreen" + } + } + + class PoolDetailsScreen( + val ids: StringPair + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "LiquidityPoolDetailsScreen" + } + } + + class LiquidityAddScreens( +// val token: NFT, + val receiver: String, + val isReceiverKnown: Boolean + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "LiquidityAddScreens" + } + } + + class LiquidityRemoveScreens( +// val token: NFT, + val receiver: String, + val isReceiverKnown: Boolean + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "LiquidityAddScreens" + } + } +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt new file mode 100644 index 0000000000..16bd7ca309 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt @@ -0,0 +1,5 @@ +package jp.co.soramitsu.liquiditypools.navigation + +sealed interface NavAction { + object BackPressed : NavAction +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index aedb1275ad..d9e9fa8288 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -1,15 +1,18 @@ package jp.co.soramitsu.liquiditypools.impl.di -import androidx.navigation.Navigator import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl -import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter +import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor +import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.wallet.impl.presentation.WalletRouter @Module @@ -19,13 +22,14 @@ class PoolsModule { @Provides @Singleton fun providesPoolIteractor( - polkaswapRepository: PolkaswapRepository + polkaswapRepository: PolkaswapRepository, + accountRepository: AccountRepository, + polkaswapInteractor: PolkaswapInteractor, + chainRegistry: ChainRegistry ): PoolsInteractor = - PoolsInteractorImpl(polkaswapRepository) + PoolsInteractorImpl(polkaswapRepository, accountRepository, polkaswapInteractor, chainRegistry) -// @Provides -// @Singleton -// fun provideLiquidityPoolsRouter(walletRouter: WalletRouter): InternalNFTRouter = InternalNFTRouterImpl( -// walletRouter = walletRouter -// ) + @Provides + @Singleton + fun provideInnerLiquidityPoolsRouter(walletRouter: WalletRouter): InternalPoolsRouter = InternalPoolsRouterImpl() } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 38849d3631..2acac0b81a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -1,14 +1,47 @@ package jp.co.soramitsu.liquiditypools.impl.domain +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto +import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor +import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.shared_utils.extensions.toHexString +import kotlinx.coroutines.flow.Flow class PoolsInteractorImpl( private val polkaswapRepository: PolkaswapRepository, + private val accountRepository: AccountRepository, + private val polkaswapInteractor: PolkaswapInteractor, + private val chainRegistry: ChainRegistry, ) : PoolsInteractor { override suspend fun getBasicPools(): List { return polkaswapRepository.getBasicPools() } + + override suspend fun getPoolCacheOfCurAccount( + tokenFromId: String, + tokenToId: String + ): CommonUserPoolData? { + val wallet = accountRepository.getSelectedMetaAccount() + val chainId = polkaswapInteractor.polkaswapChainId + val chain = chainRegistry.getChain(chainId) + val address = wallet.address(chain) + return polkaswapRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) + } + + override suspend fun getUserPoolData( + address: String, + baseTokenId: String, + tokenId: ByteArray + ): PoolDataDto? { +// return polkaswapRepository.getPoolOfAccount(address, baseTokenId, tokenId.toHexString(true), polkaswapInteractor.polkaswapChainId) + return polkaswapRepository.getUserPoolData(address, baseTokenId, tokenId) + + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt new file mode 100644 index 0000000000..825b71a62b --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -0,0 +1,38 @@ +package jp.co.soramitsu.liquiditypools.impl.navigation + +import java.util.Stack +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.liquiditypools.navigation.NavAction +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.onEach + +class InternalPoolsRouterImpl: InternalPoolsRouter { + private val routesStack = Stack() + + private val mutableActionsFlow = + MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + private val mutableRoutesFlow = + MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + override fun createNavGraphRoutesFlow(): Flow = mutableRoutesFlow.onEach { routesStack.push(it) } + override fun createNavGraphActionsFlow(): Flow = + mutableActionsFlow.onEach { if (it is NavAction.BackPressed && !routesStack.isEmpty()) routesStack.pop() } + + + override fun back() { + mutableActionsFlow.tryEmit(NavAction.BackPressed) + } + + override fun openDetailsPoolScreen(ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(ids)) + } + + override fun openPoolListScreen() { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen()) + } +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt index fede6fd954..de3a8ac736 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt @@ -1,29 +1,104 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Build +import android.os.Bundle +import android.view.KeyEvent import android.widget.FrameLayout +import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment -import jp.co.soramitsu.common.compose.component.BottomSheetScreen @AndroidEntryPoint class AllPoolsFragment : BaseComposeBottomSheetDialogFragment() { override val viewModel: AllPoolsViewModel by viewModels() + // Compose BackHandler does not work in DialogFragments, nor does BackPressedDispatcher + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + super.onCreateDialog(savedInstanceState).apply { + setOnKeyListener { _, keyCode, event -> + val isBackPressDetected = + keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP + + if (isBackPressDetected) { + viewModel.onNavigationClick() + } + + return@setOnKeyListener isBackPressDetected + } + } + } else { + // Call to super.onBackPressed() will cancel dialog as default behavior + object : BottomSheetDialog(requireContext(), theme) { + @SuppressLint("MissingSuperCall") + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onBackPressed() { + viewModel.onNavigationClick() + } + } + } + } + @Composable override fun Content(padding: PaddingValues) { - val state by viewModel.state.collectAsState() - BottomSheetScreen { - AllPoolsScreen( - state, - callback = viewModel, + val navController = rememberNavController() + + SetupNavDestinationChangedListener( + navController = navController, + onNavDestinationChanged = remember { + viewModel::onDestinationChanged + } + ) + + AllPoolsNavRoot( + viewModel = viewModel, ) + } + + @Composable + private inline fun SetupNavDestinationChangedListener( + navController: NavController, + crossinline onNavDestinationChanged: (newRoute: String) -> Unit + ) { + val lifecycleOwner = LocalLifecycleOwner.current + + DisposableEffect(lifecycleOwner) { + val onDestinationChangedListener = + NavController.OnDestinationChangedListener { _, destination, _ -> + onNavDestinationChanged(destination.route!!) + } + + val lifecycleObserver = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_START -> + navController.addOnDestinationChangedListener(onDestinationChangedListener) + + Lifecycle.Event.ON_STOP -> + navController.removeOnDestinationChangedListener( + onDestinationChangedListener + ) + + else -> Unit + } + } + + lifecycleOwner.lifecycle.addObserver(lifecycleObserver) + onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index bd5465980e..32eaaef7a8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -2,6 +2,7 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools import android.graphics.Paint.Align import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,9 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -28,6 +33,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import jp.co.soramitsu.androidfoundation.compose.sharedViewModel import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.R.drawable import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder @@ -41,8 +53,14 @@ import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.common.utils.clickableWithNoIndication import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.ui_core.resources.Dimens import jp.co.soramitsu.ui_core.theme.customColors +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach data class AllPoolsState( val pools: List = listOf() @@ -55,6 +73,86 @@ interface AllPoolsScreenInterface { fun onMoreClick() } + + +@Composable +fun AllPoolsNavRoot( + viewModel: AllPoolsViewModel, +) { + val navController = rememberNavController() + LaunchedEffect(Unit) { + viewModel.navGraphRoutesFlow.onEach { + println("!!! LaunchedEffect(Unit) { viewModel.navGraphRoutesFlow.onEach = ${it.routeName}") + navController.navigate(it.routeName) + }.launchIn(this) + + viewModel.navGraphActionsFlow.onEach { + println("!!! LaunchedEffect(Unit) { viewModel.navGraphActionsFlow.onEach = $it") + + when (it) { + is NavAction.BackPressed -> { + val isBackNavigationSuccess = navController.popBackStack() + + val currentRoute = navController.currentDestination?.route + val loadingRoute = LiquidityPoolsNavGraphRoute.Loading.routeName + + if (currentRoute == loadingRoute || !isBackNavigationSuccess) { + viewModel.exitFlow() + } + } + } + }.launchIn(this) + } + + NavHost( + startDestination = LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName, + contentAlignment = Alignment.TopCenter, + navController = navController, + modifier = Modifier +// .padding(padding) + .fillMaxSize(), + ) { + + composable(LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName) { + val allPoolsScreenState by viewModel.state.collectAsState() + BottomSheetScreen { + AllPoolsScreen( + state = allPoolsScreenState, + callback = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName) { + val poolListState by viewModel.poolListState.collectAsState() + BottomSheetScreen { + PoolListScreen( + state = poolListState, + callback = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName) { + val poolDetailState by viewModel.poolDetailState.collectAsState() + BottomSheetScreen { + PoolDetailsScreen ( + state = poolDetailState, + callbacks = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.Loading.routeName) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { CircularProgressIndicator() } + } + + } +} + @Composable fun AllPoolsScreen( state: AllPoolsState, @@ -132,7 +230,7 @@ private fun PoolGroupHeader(callback: AllPoolsScreenInterface) { modifier = Modifier .padding(start = 8.dp) .align(Alignment.CenterVertically) - .clickableWithNoIndication(callback::onMoreClick) + .clickable(onClick = callback::onMoreClick) ) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt index 02e21f0fe3..5bb29dc1ec 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt @@ -3,31 +3,78 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools import dagger.hilt.android.lifecycle.HiltViewModel import java.math.BigDecimal import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat -import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsState +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreenInterface +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListState +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.wallet.impl.domain.model.Asset +import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch @HiltViewModel class AllPoolsViewModel @Inject constructor( private val poolsInteractor: PoolsInteractor, - private val poolsRouter: LiquidityPoolsRouter -) : BaseViewModel(), AllPoolsScreenInterface { +// private val coroutinesStore: CoroutinesStore, + private val poolsRouter: LiquidityPoolsRouter, + private val innerPoolsRouter: InternalPoolsRouter, + private val accountInteractor: AccountInteractor, +) : BaseViewModel(), AllPoolsScreenInterface, PoolListScreenInterface, PoolDetailsCallbacks { + val navGraphRoutesFlow: StateFlow = + innerPoolsRouter.createNavGraphRoutesFlow().stateIn( + scope = CoroutineScope(Dispatchers.Main.immediate), + started = SharingStarted.Eagerly, + initialValue = LiquidityPoolsNavGraphRoute.AllPoolsScreen + ) + val navGraphActionsFlow: SharedFlow = + innerPoolsRouter.createNavGraphActionsFlow().shareIn( + scope = CoroutineScope(Dispatchers.Main.immediate), + started = SharingStarted.Eagerly, + replay = 1 + ) + + private val enteredAssetQueryFlow = MutableStateFlow("") private val _state = MutableStateFlow(AllPoolsState()) - val pools = flowOf { - poolsInteractor.getBasicPools().sortedWith { o1, o2 -> + private val _poolListState = MutableStateFlow(PoolListState()) + private val _poolDetailState = MutableStateFlow(PoolDetailsState()) + val pools = combine( + flowOf { poolsInteractor.getBasicPools() }, + enteredAssetQueryFlow + ) { pools, query -> + pools.filter { + it.baseToken.isMatchFilter(query) + }.sortedWith { o1, o2 -> compareNullDesc(o1.tvl, o2.tvl) } }.map { @@ -35,50 +82,120 @@ class AllPoolsViewModel @Inject constructor( }.share() val state = _state.asStateFlow() + val poolListState = _poolListState.asStateFlow() + val poolDetailState = _poolDetailState.asStateFlow() + + private val poolDetailsScreenArgsFlow = innerPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(this, SharingStarted.Eagerly, 1) init { subscribeScreenState() } + private fun Asset.isMatchFilter(filter: String): Boolean = + this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || + this.token.configuration.id.lowercase().contains(filter.lowercase()) + private fun subscribeScreenState() { pools.onEach { _state.value = _state.value.copy(pools = it) + _poolListState.value = _poolListState.value.copy(pools = it) + }.launchIn(this) + + poolDetailsScreenArgsFlow.onEach { + println("!!! AllPoolsViewModel subscribeScreenState poolDetailsScreenArgsFlow it.ids = ${it.ids}") + requestPoolDetails(it.ids)?.let { + println("!!! AllPoolsViewModel subscribeScreenState requestPoolDetails got state = $it") + + _poolDetailState.value = it + } }.launchIn(this) } + suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { + val soraChain = accountInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() + val baseAsset = soraChain.assets.firstOrNull { it.id == ids.first } + val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } + val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") + val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") + println("!!! ALVM requestPoolDetails for $ids") + val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { + PoolDetailsState( + originTokenIcon = GradientIconData(baseAsset.iconUrl, null), + destinationTokenIcon = GradientIconData(targetAsset.iconUrl, null), + fromTokenSymbol = baseAsset.symbol, + toTokenSymbol = targetAsset.symbol, + tvl = null, + apy = null + ) + } +// val reter_old = poolsInteractor.getPoolCacheOfCurAccount(ids.first, ids.second)?.let { +// PoolDetailsState( +// originTokenIcon = GradientIconData(it.basic.baseToken.token.configuration.iconUrl, null), +// destinationTokenIcon = GradientIconData(it.basic.targetToken?.token?.configuration?.iconUrl, null), +// fromTokenSymbol = it.basic.baseToken.token.configuration.symbol, +// toTokenSymbol = it.basic.targetToken?.token?.configuration?.symbol, +// tvl = null, +// apy = null +// ) +// } + return retur + } + override fun onPoolClicked(pair: StringPair) { println("!!! CLIKED ON PoolPair: $pair") + innerPoolsRouter.openDetailsPoolScreen(pair) } override fun onNavigationClick() { println("!!! CLIKED onNavigationClick") - exitFlow() + innerPoolsRouter.back() +// exitFlow() } override fun onCloseClick() { println("!!! CLIKED onCloseClick") - exitFlow() + innerPoolsRouter.back() +// exitFlow() } override fun onMoreClick() { println("!!! CLIKED onMoreClick") + innerPoolsRouter.openPoolListScreen() + } + override fun onAssetSearchEntered(value: String) { + enteredAssetQueryFlow.value = value + } + fun onDestinationChanged(route: String) { + println("!!! onDestinationChanged: $route") } fun exitFlow() { poolsRouter.back() } + + override fun onSupplyLiquidityClick() { + println("!!! onSupplyLiquidityClick") + } + + override fun onRemoveLiquidityClick() { + println("!!! onRemoveLiquidityClick") + } } -private fun BasicPoolData.toListItemState(): BasicPoolListItemState { +fun BasicPoolData.toListItemState(): BasicPoolListItemState { val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) ?.multiply(this.baseReserves) return BasicPoolListItemState( - ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.id.orEmpty()), // todo + ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.token?.configuration?.id.orEmpty()), // todo token1Icon = this.baseToken.token.configuration.iconUrl, - token2Icon = this.targetToken?.iconUrl.orEmpty(), - text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.symbol}", + token2Icon = this.targetToken?.token?.configuration?.iconUrl.orEmpty(), + text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}", text2 = tvl?.formatFiat().orEmpty(), text3 = this.sbapy?.let { "%s%%".format(it.toBigDecimal().formatCrypto()) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt new file mode 100644 index 0000000000..07b6b6e7cc --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -0,0 +1,227 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.DoubleGradientIcon +import jp.co.soramitsu.common.compose.component.GradientIconState +import jp.co.soramitsu.common.compose.component.GrayButton +import jp.co.soramitsu.common.compose.component.H4Bold +import jp.co.soramitsu.common.compose.component.InfoTable +import jp.co.soramitsu.common.compose.component.MarginHorizontal +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.NavigationIconButton +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData + +data class PoolDetailsState( + val originTokenIcon: GradientIconData? = null, + val destinationTokenIcon: GradientIconData? = null, + val fromTokenSymbol: String? = null, + val toTokenSymbol: String? = null, + val tvl: String? = null, + val apy: String? = null +) + +interface PoolDetailsCallbacks { + + fun onNavigationClick() + + fun onSupplyLiquidityClick() + fun onRemoveLiquidityClick() +} + +@Composable +fun PoolDetailsScreen( + state: PoolDetailsState, + callbacks: PoolDetailsCallbacks +) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Toolbar(callbacks) + + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MarginVertical(margin = 16.dp) + + DoubleGradientIcon( + leftImage = provideGradientIconState(state.originTokenIcon), + rightImage = provideGradientIconState(state.destinationTokenIcon) + ) + + Row( + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = state.fromTokenSymbol.orEmpty(), + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End + ) + + MarginHorizontal(margin = 8.dp) + + Icon( + painter = painterResource(jp.co.soramitsu.common.R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + + MarginHorizontal(margin = 8.dp) + + Text( + text = state.toTokenSymbol.orEmpty(), + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start + ) + } + + InfoTable( + modifier = Modifier.padding(top = 24.dp), + items = listOf( + TitleValueViewState( + title = "TVL", + value = state.tvl + ), + TitleValueViewState( + title = "Strategic bonus APY", + value = state.apy, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) + ), + TitleValueViewState( + title = "Rewards payout in", + value = "pswap" + ) + ) + ) + + MarginVertical(margin = 24.dp) + + AccentButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Supply liquidity", + onClick = callbacks::onSupplyLiquidityClick + ) + + MarginVertical(margin = 8.dp) + GrayButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Remove liquidity", + onClick = callbacks::onRemoveLiquidityClick + ) + + MarginVertical(margin = 8.dp) + } + } +} + +@Composable +private fun Toolbar(callback: PoolDetailsCallbacks) { + Row( + modifier = Modifier + .wrapContentHeight() + .padding(bottom = 12.dp) + ) { + NavigationIconButton( + modifier = Modifier.padding(start = 16.dp), + onNavigationClick = callback::onNavigationClick + ) + + H4Bold( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + text = "Pools details", + textAlign = TextAlign.Center + ) + + NavigationIconButton( + modifier = Modifier + .align(Alignment.Top) + .padding(end = 16.dp), + navigationIconResId = jp.co.soramitsu.common.R.drawable.ic_cross_32, + onNavigationClick = callback::onNavigationClick + ) + } +} + +private fun provideGradientIconState(gradientIconData: GradientIconData?): GradientIconState { + val url = gradientIconData?.url + return if (url == null) { + GradientIconState.Local( + res = R.drawable.ic_fearless_logo + ) + } else { + GradientIconState.Remote( + url = url, + color = gradientIconData.color + ) + } +} + + +@Preview() +@Composable +private fun PreviewPoolDetailsScreen() { + BottomSheetScreen { + PoolDetailsScreen( + state = PoolDetailsState( + originTokenIcon = GradientIconData(null, null), + destinationTokenIcon = GradientIconData(null, null), + fromTokenSymbol = "XOR", + toTokenSymbol = "ETH", + apy = "23.3%", + tvl = "$34.999 TVL", + ), + callbacks = object : PoolDetailsCallbacks { + override fun onNavigationClick() {} + override fun onSupplyLiquidityClick() {} + override fun onRemoveLiquidityClick() {} + }, + ) + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt new file mode 100644 index 0000000000..6efbbbcb72 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -0,0 +1,147 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.poollist + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.CorneredInput +import jp.co.soramitsu.common.compose.component.H4Bold +import jp.co.soramitsu.common.compose.component.NavigationIconButton +import jp.co.soramitsu.common.compose.theme.white04 +import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItem +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItemState +import jp.co.soramitsu.ui_core.resources.Dimens + +data class PoolListState( + val pools: List = listOf(), + val searchQuery: String? = null +) + +interface PoolListScreenInterface { + fun onPoolClicked(pair: StringPair) + fun onNavigationClick() + fun onCloseClick() + fun onAssetSearchEntered(value: String) + +} + +@Composable +fun PoolListScreen( + state: PoolListState, + callback: PoolListScreenInterface +) { + Column( + modifier = Modifier.fillMaxSize(), + ) { + Toolbar(callback) + + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + CorneredInput( + state = state.searchQuery, + borderColor = white04, + hintLabel = stringResource(id = R.string.manage_assets_search_hint), + onInput = callback::onAssetSearchEntered + ) + } + + val listState = rememberLazyListState() + + LazyColumn( + state = listState, + modifier = Modifier + .wrapContentHeight() + ) { + items(state.pools) { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } + } + } +} + +@Composable +private fun Toolbar(callback: PoolListScreenInterface) { + Row( + modifier = Modifier + .wrapContentHeight() + .padding(bottom = 12.dp) + ) { + NavigationIconButton( + modifier = Modifier.padding(start = 16.dp), + onNavigationClick = callback::onNavigationClick + ) + + H4Bold( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + text = "Your pools", + textAlign = TextAlign.Center + ) + + NavigationIconButton( + modifier = Modifier + .align(Alignment.Top) + .padding(end = 16.dp), + navigationIconResId = jp.co.soramitsu.common.R.drawable.ic_cross_32, + onNavigationClick = callback::onCloseClick + ) + } +} + +@Preview +@Composable +private fun PreviewPoolListScreen() { + val itemState = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "XOR-VAL", + text2 = "123.4M", + text3 = "1234.3%", + text4 = "Earn SWAP", + ) + + val items = listOf( + itemState, + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", text3 = "TEXT3", text4 = "TEXT4"), + itemState.copy(text1 = "text1", text2 = "text2", text3 = "text3", text4 = "text4"), + ) + Column { + BottomSheetScreen { + PoolListScreen( + state = PoolListState( + pools = items, + ), + callback = object : PoolListScreenInterface { + override fun onPoolClicked(pair: StringPair) {} + override fun onNavigationClick() {} + override fun onCloseClick() {} + override fun onAssetSearchEntered(value: String) {} + }, + ) + } + } +} diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index cdd79d7c9e..772781da97 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -7,6 +7,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigInteger import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData interface PolkaswapRepository { suspend fun getAvailableDexes(chainId: ChainId): List @@ -56,4 +57,17 @@ interface PolkaswapRepository { ): Result suspend fun getBasicPools(): List + + suspend fun getPoolOfAccount( + address: String?, + tokenFromId: String, + tokenToId: String, + chainId: String + ): CommonUserPoolData? + + suspend fun getUserPoolData( + address: String, + baseTokenId: String, + tokenId: ByteArray + ): PoolDataDto? } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt new file mode 100644 index 0000000000..875bd84ad5 --- /dev/null +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt @@ -0,0 +1,13 @@ +package jp.co.soramitsu.polkaswap.api.data + +import java.math.BigInteger + +data class PoolDataDto( + val baseAssetId: String, + val assetId: String, + val reservesFirst: BigInteger, + val reservesSecond: BigInteger, + val totalIssuance: BigInteger, + val poolProvidersBalance: BigInteger, + val reservesAccount: String, +) diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt index 38887f0acc..1e2b94e663 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt @@ -2,13 +2,13 @@ package jp.co.soramitsu.polkaswap.api.domain.models import android.os.Parcelable import java.math.BigDecimal -import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.wallet.impl.domain.model.Asset import kotlinx.parcelize.Parcelize data class BasicPoolData( // val baseToken: Token, // val targetToken: Token, - val baseToken: jp.co.soramitsu.wallet.impl.domain.model.Asset, + val baseToken: Asset, val targetToken: Asset?, val baseReserves: BigDecimal, val targetReserves: BigDecimal, diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt new file mode 100644 index 0000000000..ea3be99287 --- /dev/null +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt @@ -0,0 +1,57 @@ +package jp.co.soramitsu.polkaswap.api.domain.models + +import java.math.BigDecimal + +data class CommonUserPoolData( + val basic: BasicPoolData, + val user: UserPoolData, +) { + fun printFiat(): Pair? = printFiatInternal(basic, user) +} + +data class CommonPoolData( + val basic: BasicPoolData, + val user: UserPoolData?, +) { + fun printFiat(): Pair? = printFiatInternal(basic, user) +} + +data class UserPoolData( + val address: String?, + val basePooled: BigDecimal, + val targetPooled: BigDecimal, + val poolShare: Double, + val poolProvidersBalance: BigDecimal, +) + +val List.fiatSymbol: String + get() { + return getOrNull(0)?.basic?.fiatSymbol ?: "$" //OptionsProvider.fiatSymbol + } + +private fun printFiatInternal(basic: BasicPoolData, user: UserPoolData?): Pair? { + return null +// if (user == null) return null +// val f1 = user.basePooled.applyFiatRate(basic.baseToken.token.fiatRate) +// val f2 = user.targetPooled.applyFiatRate(basic.targetToken?.token?.fiatRate) +// if (f1 == null || f2 == null) return null +// val change1 = basic.baseToken.fiatPriceChange ?: 0.0 +// val change2 = basic.targetToken.fiatPriceChange ?: 0.0 +// val price1 = basic.baseToken.fiatPrice ?: return null +// val price2 = basic.targetToken.fiatPrice ?: return null +// val newPoolFiat = f1 + f2 +// val oldPoolFiat = calcAmount(price1 / (1 + change1), user.basePooled) + +// calcAmount(price2 / (1 + change2), user.targetPooled) +// val changePool = fiatChange(oldPoolFiat, newPoolFiat) +// return newPoolFiat to changePool +} + +fun BasicPoolData.isFilterMatch(filter: String): Boolean { + val t1 = targetToken?.token?.configuration?.name?.lowercase()?.contains(filter.lowercase()) == true || + targetToken?.token?.configuration?.symbol?.lowercase()?.contains(filter.lowercase()) == true || + targetToken?.token?.configuration?.id?.lowercase()?.contains(filter.lowercase()) == true + val t2 = baseToken.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + baseToken.token.configuration.symbol.lowercase().contains(filter.lowercase()) || + baseToken.token.configuration.id.lowercase().contains(filter.lowercase()) + return t1 || t2 +} diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 6a47ea807b..e0c3312201 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -19,7 +19,10 @@ import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.api.models.backStrings @@ -27,13 +30,16 @@ import jp.co.soramitsu.polkaswap.api.models.toFilters import jp.co.soramitsu.polkaswap.api.models.toMarkets import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.bindings.bindDexInfos import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.swap +import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.runtime.network.subscriptionFlowCatching import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.shared_utils.extensions.toHexString import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex @@ -43,6 +49,7 @@ import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey import jp.co.soramitsu.shared_utils.scale.Schema import jp.co.soramitsu.shared_utils.scale.sizedByteArray import jp.co.soramitsu.shared_utils.scale.uint128 +import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId import jp.co.soramitsu.shared_utils.wsrpc.exception.RpcException import jp.co.soramitsu.shared_utils.wsrpc.executeAsync import jp.co.soramitsu.shared_utils.wsrpc.mappers.nonNull @@ -51,10 +58,17 @@ import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojoList import jp.co.soramitsu.shared_utils.wsrpc.mappers.scale import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest +import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository +import jp.co.soramitsu.wallet.impl.domain.model.Asset import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map class PolkaswapRepositoryImpl @Inject constructor( private val remoteConfigFetcher: RemoteConfigFetcher, @@ -89,35 +103,39 @@ class PolkaswapRepositoryImpl @Inject constructor( } override fun observePoolXYKReserves(chainId: ChainId, fromTokenId: String, toTokenId: String): Flow { - return flow { emit(waitForChain(chainId)) }.flatMapLatest { remoteStorage.observe( - chainId = chainId, - keyBuilder = { - val from = Struct.Instance( - mapOf("code" to fromTokenId.fromHex().toList().map { it.toInt().toBigInteger() }) - ) - val to = Struct.Instance( - mapOf("code" to toTokenId.fromHex().toList().map { it.toInt().toBigInteger() }) - ) - it.metadata.poolXYK()?.storage("Reserves")?.storageKey(it, from, to) + return flow { emit(waitForChain(chainId)) }.flatMapLatest { + remoteStorage.observe( + chainId = chainId, + keyBuilder = { + val from = Struct.Instance( + mapOf("code" to fromTokenId.fromHex().toList().map { it.toInt().toBigInteger() }) + ) + val to = Struct.Instance( + mapOf("code" to toTokenId.fromHex().toList().map { it.toInt().toBigInteger() }) + ) + it.metadata.poolXYK()?.storage("Reserves")?.storageKey(it, from, to) + } + ) { scale, _ -> + scale.orEmpty() } - ) { scale, _ -> - scale.orEmpty() - }} + } } override fun observePoolTBCReserves(chainId: ChainId, tokenId: String): Flow { - return flow { emit(waitForChain(chainId)) }.flatMapLatest { remoteStorage.observe( - chainId = chainId, - keyBuilder = { - val token = Struct.Instance( - mapOf("code" to tokenId.fromHex().toList().map { it.toInt().toBigInteger() }) - ) - it.metadata.poolTBC()?.storage("CollateralReserves")?.storageKey(it, token) + return flow { emit(waitForChain(chainId)) }.flatMapLatest { + remoteStorage.observe( + chainId = chainId, + keyBuilder = { + val token = Struct.Instance( + mapOf("code" to tokenId.fromHex().toList().map { it.toInt().toBigInteger() }) + ) + it.metadata.poolTBC()?.storage("CollateralReserves")?.storageKey(it, token) + } + ) { scale, _ -> + scale.orEmpty() } - ) { scale, _ -> - scale.orEmpty() } - }} + } // Because if we get chain from the ChainRegistry, it will emit a chain // only after runtime for this chain will be ready @@ -287,17 +305,18 @@ class PolkaswapRepositoryImpl @Inject constructor( } fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() + object TotalIssuance : Schema() { val totalIssuance by uint128() } - suspend fun getPoolTotalIssuances( + suspend fun getPoolTotalIssuances( reservesAccountId: ByteArray, ): BigInteger? { val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) - ?.storage("TotalIssuances") - ?.storageKey(runtimeOrNull, reservesAccountId) + ?.storage("TotalIssuances") + ?.storageKey(runtimeOrNull, reservesAccountId) ?: return null return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), @@ -324,7 +343,6 @@ class PolkaswapRepositoryImpl @Inject constructor( val list = mutableListOf() val soraChain = chainRegistry.getChain(soraMainChainId) - val assets = soraChain.assets val wallet = accountRepository.getSelectedMetaAccount() @@ -364,7 +382,7 @@ class PolkaswapRepositoryImpl @Inject constructor( }?.let { mapBalance(it, asset.token.configuration.precision) } - val targetAsset = assets.firstOrNull { it.currencyId == targetToken } + val targetAsset = soraAssets.firstOrNull { it.token.configuration.currencyId == targetToken } val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" val element = BasicPoolData( @@ -382,7 +400,7 @@ class PolkaswapRepositoryImpl @Inject constructor( element ) // todo remove - if (list.size > 10) return list + if (list.size > 5) return list } } } @@ -393,4 +411,371 @@ class PolkaswapRepositoryImpl @Inject constructor( return list } + + private fun subscribeAccountPoolProviders( + address: String, + reservesAccount: ByteArray, + ): Flow = flow { + val poolProvidersKey = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("TotalIssuances") + .storageKey( + it, + reservesAccount, + address.toAccountId() + ) + } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") + val poolProvidersFlow = chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(poolProvidersKey), + "state_unsubscribeStorage", + ).map { + it.map { it.storageChange().getSingleChange().orEmpty() } + }.map { + it.getOrNull().orEmpty() + } + emitAll(poolProvidersFlow) + } + + override suspend fun getUserPoolData( + address: String, + baseTokenId: String, + tokenId: ByteArray + ): PoolDataDto? { + println("!!! getUserPoolData") + val reserves = getPairWithXorReserves(baseTokenId, tokenId) + val totalIssuanceAndProperties = + getPoolTotalIssuanceAndProperties(baseTokenId, tokenId, address) + + if (reserves == null || totalIssuanceAndProperties == null) { + return null + } + val reservesAccount = chainRegistry.getChain(soraMainChainId).addressOf(totalIssuanceAndProperties.third) + + return PoolDataDto( + baseTokenId, + tokenId.toHexString(true), + reserves.first, + reserves.second, + totalIssuanceAndProperties.first, + totalIssuanceAndProperties.second, + reservesAccount, + ) + } + + private suspend fun getPoolTotalIssuanceAndProperties( + baseTokenId: String, + tokenId: ByteArray, + address: String + ): Triple? { + return getPoolReserveAccount(baseTokenId, tokenId)?.let { account -> + getPoolTotalIssuances( + account + )?.let { + val provider = getPoolProviders( + account, + address + ) + Triple(it, provider, account) + } + } + } + + object PoolProviders : Schema() { + val poolProviders by uint128() + } + + private suspend fun getPoolProviders( + reservesAccountId: ByteArray, + currentAddress: String + ): BigInteger { + val storageKey = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("PoolProviders").storageKey( + it, + reservesAccountId, + currentAddress.toAccountId() + ) + } ?: return BigInteger.ZERO + return runCatching { + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(PoolProviders), + ) + .let { storage -> + storage.result?.let { + it[it.schema.poolProviders] + } ?: BigInteger.ZERO + } + }.getOrElse { + it.printStackTrace() + throw it + } + } + + object ReservesResponse : Schema() { + val first by uint128() + val second by uint128() + } + + private suspend fun getPairWithXorReserves( + baseTokenId: String, + tokenId: ByteArray + ): Pair? { + println("!!! getPairWithXorReserves") + val storageKey = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.reservesKey(baseTokenId, tokenId) + ?: return null + println("!!! getPairWithXorReserves storageKey = $storageKey") + return try { + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(ReservesResponse), + ) + .result + ?.let { storage -> + storage[storage.schema.first] to storage[storage.schema.second] + } + } catch (e: Exception) { + println("!!! getPairWithXorReserves error = ${e.message}") + + e.printStackTrace() + throw e + } + } + + override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { + return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { + it.user.address == address + } + } + + suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { +// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) +// val socketService = chainRegistry.getConnection(chainId).socketService +// socketService.executeAsyncCatching() + val tokensPair: List>>? = address?.let { + getUserPoolsTokenIds(it) + } + + val pools = mutableListOf() + + tokensPair?.forEach { (baseTokenId, tokensId) -> + tokensId.mapNotNull { tokenId -> + getUserPoolData(address, baseTokenId, tokenId) + }.forEach pool@{ poolDataDto -> + val metaId = accountRepository.getSelectedLightMetaAccount().id + val assets = walletRepository.getAssets(metaId) + val token = assets.firstOrNull { + it.token.configuration.currencyId == poolDataDto.assetId + } ?: return@pool + val baseToken = assets.firstOrNull { + it.token.configuration.currencyId == baseTokenId + } ?: return@pool + val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 + val tokenPrecision = token?.token?.configuration?.precision ?: 0 + + val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) + + val basePooled = PolkaswapFormulas.calculatePooledValue( + mapBalance( + poolDataDto.reservesFirst, + xorPrecision + ), + mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ), + mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + baseToken?.token?.configuration?.precision, + ) + val secondPooled = PolkaswapFormulas.calculatePooledValue( + mapBalance( + poolDataDto.reservesSecond, + tokenPrecision + ), + mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ), + mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + token?.token?.configuration?.precision, + ) + val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( + mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ), + mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + ) + val userPoolData = CommonUserPoolData( + basic = BasicPoolData( + baseToken = baseToken, + targetToken = token, + baseReserves = mapBalance( + poolDataDto.reservesFirst, + xorPrecision + ), + targetReserves = mapBalance( + poolDataDto.reservesSecond, + tokenPrecision + ), + totalIssuance = mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + reserveAccount = poolDataDto.reservesAccount, + sbapy = apy, + ), + user = UserPoolData( + address = address, + basePooled = basePooled, + targetPooled = secondPooled, + share, + mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ), + ), + ) + pools.add(userPoolData) + } + } + return pools + } + + fun RuntimeSnapshot.reservesKey(baseTokenId: String, tokenId: ByteArray): String = + this.metadata.module(Modules.POOL_XYK) + .storage("Reserves") + .storageKey( + this, + baseTokenId.mapCodeToken(), + tokenId.mapCodeToken(), + ) + + @Suppress("UNCHECKED_CAST") + fun SubscriptionChange.storageChange(): SubscribeStorageResult { + val result = params.result as? Map<*, *> ?: throw IllegalArgumentException("${params.result} is not a valid storage result") + + val block = result["block"] as? String ?: throw IllegalArgumentException("$result is not a valid storage result") + val changes = result["changes"] as? List> ?: throw IllegalArgumentException("$result is not a valid storage result") + + return SubscribeStorageResult(block, changes) + } + + private fun subscribeToPoolData( + baseTokenId: String, + tokenId: ByteArray, + reservesAccount: ByteArray, + ): Flow = flow { + val reservesKey = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.reservesKey(baseTokenId, tokenId) + + val reservesFlow = reservesKey?.let { + chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(reservesKey), + "state_unsubscribeStorage", + ).map { + it.map { it.storageChange().getSingleChange().orEmpty() } + }.map { + it.getOrNull().orEmpty() + } + } ?: emptyFlow() + + val totalIssuanceKey = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("TotalIssuances") + .storageKey(it, reservesAccount) + } + val totalIssuanceFlow = totalIssuanceKey?.let { + chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(totalIssuanceKey), + "state_unsubscribeStorage", + ).map { + it.getOrNull()?.storageChange()?.getSingleChange().orEmpty() + } + } ?: emptyFlow() + + val resultFlow = reservesFlow + .combine(totalIssuanceFlow) { reservesString, totalIssuanceString -> + (reservesString + totalIssuanceString).take(5) + } + + emitAll(resultFlow) + } + + // changes are in format [[storage key, value], [..], ..] + class SubscribeStorageResult(val block: String, val changes: List>) { + fun getSingleChange() = changes.first()[1] + } + + fun Struct.Instance.getTokenId() = get>("code") + ?.map { (it as BigInteger).toByte() } + ?.toByteArray() + + + suspend fun getUserPoolsTokenIdsKeys(address: String): List { + val accountPoolsKey = chainRegistry.getRuntimeOrNull(soraMainChainId)?.accountPoolsKey(address) + chainRegistry.awaitConnection(soraMainChainId).socketService + return runCatching { + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = StateKeys(listOfNotNull(accountPoolsKey)), + mapper = pojoList().nonNull() + ) + }.onFailure { + println("!!! getUserPoolsTokenIdsKeys error: ${it.message}") + it.printStackTrace() + } + .getOrThrow() + } + + suspend fun getUserPoolsTokenIds( + address: String + ): List>> { + return runCatching { + val storageKeys = getUserPoolsTokenIdsKeys(address) + storageKeys.map { storageKey -> + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull(), + ) + .let { storage -> + val storageType = + chainRegistry.getRuntimeOrNull(soraMainChainId)?.metadata?.module(Modules.POOL_XYK) + ?.storage("AccountPools")?.type?.value!! + val storageRawData = + storageType.fromHex(chainRegistry.getRuntimeOrNull(soraMainChainId)!!, storage) + val tokens: List = if (storageRawData is List<*>) { + storageRawData.filterIsInstance() + .mapNotNull { struct -> + struct.getTokenId() + } + } else { + emptyList() + } + storageKey.assetIdFromKey() to tokens + } + } + }.onFailure { + println("!!! getUserPoolsTokenIds onFailure") +// it.printStackTrace() + }.getOrThrow() + } + + fun RuntimeSnapshot.accountPoolsKey(address: String): String = + this.metadata.module(Modules.POOL_XYK) + .storage("AccountPools") + .storageKey(this, address.toAccountId()) + } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt new file mode 100644 index 0000000000..38cd32d050 --- /dev/null +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt @@ -0,0 +1,107 @@ +package jp.co.soramitsu.polkaswap.impl.util + +import java.math.BigDecimal +import jp.co.soramitsu.androidfoundation.format.Big100 +import jp.co.soramitsu.androidfoundation.format.divideBy +import jp.co.soramitsu.androidfoundation.format.equalTo +import jp.co.soramitsu.androidfoundation.format.safeDivide +import jp.co.soramitsu.polkaswap.api.models.WithDesired + +object PolkaswapFormulas { + + fun calculatePooledValue( + reserves: BigDecimal, + poolProvidersBalance: BigDecimal, + totalIssuance: BigDecimal, + precision: Int? = 18 //OptionsProvider.defaultScale, + ): BigDecimal = + reserves.multiply(poolProvidersBalance).divideBy(totalIssuance, precision) + + private fun calculateShareOfPool( + poolProvidersBalance: BigDecimal, + totalIssuance: BigDecimal + ): BigDecimal = + poolProvidersBalance.divideBy(totalIssuance).multiply(Big100) + + fun calculateShareOfPoolFromAmount( + amount: BigDecimal, + amountPooled: BigDecimal, + ): Double = if (amount.equalTo(amountPooled)) 100.0 else + calculateShareOfPool(amount, amountPooled).toDouble() + + fun calculateAddLiquidityAmount( + baseAmount: BigDecimal, + reservesFirst: BigDecimal, + reservesSecond: BigDecimal, + precisionFirst: Int, + precisionSecond: Int, + desired: WithDesired, + ): BigDecimal { + return if (desired == WithDesired.INPUT) { + baseAmount.multiply(reservesSecond).safeDivide(reservesFirst, precisionSecond) + } else { + baseAmount.multiply(reservesFirst).safeDivide(reservesSecond, precisionFirst) + } + } + + fun estimateAddingShareOfPool( + amount: BigDecimal, + pooled: BigDecimal, + reserves: BigDecimal + ): BigDecimal { + return pooled + .plus(amount) + .multiply(Big100) + .safeDivide(amount.plus(reserves)) + } + + fun estimateRemovingShareOfPool( + amount: BigDecimal, + pooled: BigDecimal, + reserves: BigDecimal + ): BigDecimal = pooled + .minus(amount) + .multiply(Big100) + .safeDivide(reserves.minus(amount)) + + fun calculateMinAmount( + amount: BigDecimal, + slippageTolerance: Double, + ): BigDecimal { + return amount.minus(amount.multiply(BigDecimal.valueOf(slippageTolerance / 100))) + } + + fun calculateTokenPerTokenRate( + amount1: BigDecimal, + amount2: BigDecimal, + ): BigDecimal { + return amount1.safeDivide(amount2) + } + + fun calculateMarkerAssetDesired( + fromAmount: BigDecimal, + firstReserves: BigDecimal, + totalIssuance: BigDecimal, + ): BigDecimal = fromAmount.safeDivide(firstReserves).multiply(totalIssuance) + + fun calculateStrategicBonusAPY( + strategicBonusApy: Double? + ): Double? { + return strategicBonusApy?.times(100) + } + + fun calculateAmountByPercentage( + amount: BigDecimal, + percentage: Double, + precision: Int, + ): BigDecimal = if (percentage == 100.0) amount else + amount.multiply(percentage.toBigDecimal()) + .safeDivide(Big100, precision) + + fun calculateOneAmountFromAnother( + amount: BigDecimal, + amountPooled: BigDecimal, + otherPooled: BigDecimal, + precision: Int? = 18 //OptionsProvider.defaultScale, + ): BigDecimal = amount.multiply(otherPooled).safeDivide(amountPooled, precision) +} diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index ef4998da9e..ac77a5338d 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -318,7 +318,9 @@ class BalanceListViewModel @Inject constructor( } if (it is NFTCollection.Loaded.WithFailure) { - chainsWithFailedRequests.add(it.chainName) + println("!!! NFTCollection.Loaded.WithFailure: ${it.chainName} : ${it.throwable.message} ") + it.throwable.printStackTrace() +// chainsWithFailedRequests.add(it.chainName) } if (it is NFTCollection.Loaded.Result) { From 2b4eb223ed934ea24a6a566ddad7337c78a52b8d Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 22 Jul 2024 11:44:47 +0500 Subject: [PATCH 30/84] wip: supply liquidity --- app/build.gradle | 1 + .../root/presentation/main/MainViewModel.kt | 2 + .../main/res/navigation/main_nav_graph.xml | 5 + .../compose/component/InfoTableItemAsset.kt | 92 ++++ .../common/data/network/OptionsProvider.kt | 3 + common/src/main/res/values/strings.xml | 1 + feature-liquiditypools-api/build.gradle.kts | 5 + .../domain/interfaces/PoolsInteractor.kt | 33 ++ .../navigation/InternalPoolsRouter.kt | 7 + .../navigation/LiquidityPoolsNavGraphRoute.kt | 23 +- .../liquiditypools/navigation/NavAction.kt | 5 + feature-liquiditypools-impl/build.gradle.kts | 1 + .../liquiditypools/impl/di/PoolsModule.kt | 6 +- .../impl/domain/PoolsInteractorImpl.kt | 115 ++++- .../navigation/InternalPoolsRouterImpl.kt | 18 + .../impl/presentation/CoroutinesStore.kt | 41 ++ .../presentation/allpools/AllPoolsFragment.kt | 109 +++- .../presentation/allpools/AllPoolsScreen.kt | 93 +--- .../allpools/AllPoolsViewModel.kt | 69 ++- .../liquidityadd/LiquidityAddPresenter.kt | 484 ++++++++++++++++++ .../liquidityadd/LiquidityAddScreen.kt | 254 +++++++++ .../LiquidityAddConfirmPresenter.kt | 237 +++++++++ .../LiquidityAddConfirmScreen.kt | 267 ++++++++++ .../usecase/ValidateAddLiquidityUseCase.kt | 54 ++ feature-polkaswap-api/build.gradle.kts | 7 + .../polkaswap/api/data/LiquidityData.kt | 11 + .../polkaswap/api/data/PolkaswapRepository.kt | 31 ++ .../api/domain/PolkaswapInteractor.kt | 2 + .../sorablockexplorer/BlockExplorerManager.kt | 31 ++ .../impl/data/PolkaswapRepositoryImpl.kt | 164 +++++- .../impl/data/network/blockchain/Extrinsic.kt | 102 ++++ .../impl/di/PolkaswapFeatureBindModule.kt | 47 +- .../impl/domain/PolkaswapInteractorImpl.kt | 7 + .../domain/interfaces/WalletRepository.kt | 2 +- .../data/repository/WalletRepositoryImpl.kt | 4 +- .../balance/list/BalanceListViewModel.kt | 4 +- .../manageassets/ManageAssetsViewModel.kt | 3 +- .../runtime/multiNetwork/ChainRegistry.kt | 2 +- 38 files changed, 2186 insertions(+), 156 deletions(-) create mode 100644 common/src/main/java/jp/co/soramitsu/common/compose/component/InfoTableItemAsset.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt create mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt create mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt diff --git a/app/build.gradle b/app/build.gradle index 324435c406..a4c2644157 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -139,6 +139,7 @@ play { } dependencies { + implementation project(':android-foundation') implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':core-db') implementation project(':common') diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt index 3c01f15797..f82a3c93ca 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt @@ -40,6 +40,8 @@ class MainViewModel @Inject constructor( .asLiveData() fun navigateToSwapScreen() { + val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") +// polkaswapRouter.openAddLiquidity(xorPswap) if (polkaswapInteractor.hasReadDisclaimer) { walletRouter.openSwapTokensScreen( chainId = null, diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 50828a50ec..75973851ee 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -939,6 +939,11 @@ android:name="jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsFragment" android:label="allPoolsFragment" /> + + + %d other + %d others + Your %s pooled Reset to default Swap Swapped diff --git a/feature-liquiditypools-api/build.gradle.kts b/feature-liquiditypools-api/build.gradle.kts index 8a135dd776..2ad9196a79 100644 --- a/feature-liquiditypools-api/build.gradle.kts +++ b/feature-liquiditypools-api/build.gradle.kts @@ -37,4 +37,9 @@ dependencies { implementation(libs.sharedFeaturesCoreDep) { exclude(module = "android-foundation") } + + implementation(libs.xnetworking.basic) + implementation(libs.xnetworking.sorawallet) { + exclude(group = "jp.co.soramitsu.xnetworking", module = "basic") + } } \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index d15b42542a..a4a9abdfc2 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -1,8 +1,10 @@ package jp.co.soramitsu.liquiditypools.domain.interfaces +import java.math.BigDecimal import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.wallet.impl.domain.model.Asset interface PoolsInteractor { suspend fun getBasicPools(): List @@ -14,4 +16,35 @@ interface PoolsInteractor { baseTokenId: String, tokenId: ByteArray ): PoolDataDto? + + suspend fun calcAddLiquidityNetworkFee( + address: String, + tokenFrom: jp.co.soramitsu.core.models.Asset, + tokenTo: jp.co.soramitsu.core.models.Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? + + suspend fun isPairEnabled( + inputTokenId: String, + outputTokenId: String, + accountAddress: String + ): Boolean + + suspend fun updateApy() + + fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? + + suspend fun observeAddLiquidity( + tokenFrom: jp.co.soramitsu.core.models.Asset, + tokenTo: jp.co.soramitsu.core.models.Asset, + amountFrom: BigDecimal, + amountTo: BigDecimal, + enabled: Boolean, + presented: Boolean, + slippageTolerance: Double + ): String } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 8807f5f8ef..49647c21e1 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.liquiditypools.navigation +import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.StringPair import kotlinx.coroutines.flow.Flow @@ -8,7 +9,13 @@ interface InternalPoolsRouter { fun createNavGraphActionsFlow(): Flow fun back() + fun openAllPoolsScreen() fun openDetailsPoolScreen(ids: StringPair) + fun openAddLiquidityScreen(ids: StringPair) + fun openAddLiquidityConfirmScreen(ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) + fun openPoolListScreen() + + fun openErrorsScreen(title: String? = null, message: String) } \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 125b947aa5..6b9f8b499f 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.liquiditypools.navigation +import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId @@ -11,10 +12,7 @@ sealed interface LiquidityPoolsNavGraphRoute { override val routeName: String = "Loading" } - class AllPoolsScreen( - val chainId: ChainId, - val contractAddress: String - ) : LiquidityPoolsNavGraphRoute by Companion { + class AllPoolsScreen: LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "AllPoolsScreen" } @@ -36,16 +34,25 @@ sealed interface LiquidityPoolsNavGraphRoute { } } - class LiquidityAddScreens( -// val token: NFT, - val receiver: String, - val isReceiverKnown: Boolean + class LiquidityAddScreen( + val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "LiquidityAddScreens" } } + class LiquidityAddConfirmScreen( + val ids: StringPair, + val amountFrom: BigDecimal, + val amountTo: BigDecimal, + val apy: String + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "LiquidityAddConfirmScreen" + } + } + class LiquidityRemoveScreens( // val token: NFT, val receiver: String, diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt index 16bd7ca309..a529ef119f 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt @@ -2,4 +2,9 @@ package jp.co.soramitsu.liquiditypools.navigation sealed interface NavAction { object BackPressed : NavAction + + class ShowError( + val errorTitle: String?, + val errorText: String + ) : NavAction } \ No newline at end of file diff --git a/feature-liquiditypools-impl/build.gradle.kts b/feature-liquiditypools-impl/build.gradle.kts index 20f5b247d7..2a04356b9a 100644 --- a/feature-liquiditypools-impl/build.gradle.kts +++ b/feature-liquiditypools-impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.common) implementation(projects.runtime) implementation(projects.featurePolkaswapApi) + implementation(projects.featurePolkaswapImpl) // implementation(project(":feature-wallet-api")) // diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index d9e9fa8288..eb859b5576 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl @@ -25,9 +26,10 @@ class PoolsModule { polkaswapRepository: PolkaswapRepository, accountRepository: AccountRepository, polkaswapInteractor: PolkaswapInteractor, - chainRegistry: ChainRegistry + chainRegistry: ChainRegistry, + keypairProvider: KeypairProvider ): PoolsInteractor = - PoolsInteractorImpl(polkaswapRepository, accountRepository, polkaswapInteractor, chainRegistry) + PoolsInteractorImpl(polkaswapRepository, accountRepository, polkaswapInteractor, chainRegistry, keypairProvider) @Provides @Singleton diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 2acac0b81a..08d0e61deb 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -1,23 +1,27 @@ package jp.co.soramitsu.liquiditypools.impl.domain +import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.address -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.common.data.secrets.v1.Keypair +import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema +import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets +import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.shared_utils.extensions.toHexString -import kotlinx.coroutines.flow.Flow +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId class PoolsInteractorImpl( private val polkaswapRepository: PolkaswapRepository, private val accountRepository: AccountRepository, private val polkaswapInteractor: PolkaswapInteractor, private val chainRegistry: ChainRegistry, + private val keypairProvider: KeypairProvider, ) : PoolsInteractor { override suspend fun getBasicPools(): List { @@ -44,4 +48,107 @@ class PoolsInteractorImpl( return polkaswapRepository.getUserPoolData(address, baseTokenId, tokenId) } + + override suspend fun calcAddLiquidityNetworkFee( + address: String, + tokenFrom: jp.co.soramitsu.core.models.Asset, + tokenTo: jp.co.soramitsu.core.models.Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? { + return polkaswapRepository.calcAddLiquidityNetworkFee( + address, + tokenFrom, + tokenTo, + tokenFromAmount, + tokenToAmount, + pairEnabled, + pairPresented, + slippageTolerance, + ) + } + + override suspend fun updateApy() { + polkaswapInteractor.updatePoolsSbApy() + } + + override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? = + polkaswapInteractor.getPoolStrategicBonusAPY(reserveAccountOfPool) + + override suspend fun isPairEnabled(inputTokenId: String, outputTokenId: String, accountAddress: String): Boolean { + val dexId = polkaswapRepository.getPoolBaseTokenDexId(inputTokenId) + return polkaswapRepository.isPairAvailable( + soraMainChainId, + inputTokenId, + outputTokenId, + dexId + ) + } + + override suspend fun observeAddLiquidity( + tokenFrom: jp.co.soramitsu.core.models.Asset, + tokenTo: jp.co.soramitsu.core.models.Asset, + amountFrom: BigDecimal, + amountTo: BigDecimal, + enabled: Boolean, + presented: Boolean, + slippageTolerance: Double + ): String { + val soraChain = chainRegistry.getChain(soraMainChainId) + val metaAccount = accountRepository.getSelectedMetaAccount() + val address = metaAccount.address(soraChain) ?: return "" + + val networkFee = calcAddLiquidityNetworkFee( + address, + tokenFrom, + tokenTo, + amountFrom, + amountTo, + enabled, + presented, + slippageTolerance + ) + + val secrets = accountRepository.getMetaAccountSecrets(metaAccount.id)?.get(MetaAccountSecrets.SubstrateKeypair) + requireNotNull(secrets) + val private = secrets[KeyPairSchema.PrivateKey] + val public = secrets[KeyPairSchema.PublicKey] + val nonce = secrets[KeyPairSchema.Nonce] + val keypair = Keypair(public, private, nonce) + + val status = polkaswapRepository.observeAddLiquidity( + address, + keypair, + tokenFrom, + tokenTo, + amountFrom, + amountTo, + enabled, + presented, + slippageTolerance + ) + + + + if (status != null) { +// transactionHistoryRepository.saveTransaction( +// transactionBuilder.buildLiquidity( +// txHash = status.txHash, +// blockHash = status.blockHash, +// fee = networkFee, +// status = TransactionStatus.PENDING, +// date = Date().time, +// token1 = tokenFrom, +// token2 = tokenTo, +// amount1 = amountFrom, +// amount2 = amountTo, +// type = TransactionLiquidityType.ADD, +// ) +// ) + } + return status?.first ?: "" + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 825b71a62b..3fb4b93fb2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.navigation +import java.math.BigDecimal import java.util.Stack import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter @@ -28,11 +29,28 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableActionsFlow.tryEmit(NavAction.BackPressed) } + override fun openAllPoolsScreen() { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen()) + } + override fun openDetailsPoolScreen(ids: StringPair) { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(ids)) } + override fun openAddLiquidityScreen(ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(ids)) + } + + override fun openAddLiquidityConfirmScreen(ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(ids, amountFrom, amountTo, apy)) + } + override fun openPoolListScreen() { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen()) } + + override fun openErrorsScreen(title: String?, message: String) { + mutableActionsFlow.tryEmit(NavAction.ShowError(title, message)) + } + } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt new file mode 100644 index 0000000000..46747dc6be --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt @@ -0,0 +1,41 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation + +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.cachedOrNew +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.properties.Delegates + +@Singleton +class CoroutinesStore @Inject constructor( + private val resourceManager: ResourceManager, +) { + + val uiScope: CoroutineScope by Delegates.cachedOrNew(isCorrupted = ::isScopeCanceled) { + CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate + CoroutineExceptionHandler(::handleException)) + } + + val ioScope: CoroutineScope by Delegates.cachedOrNew(::isScopeCanceled) { + CoroutineScope(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler(::handleException)) + } + + private fun isScopeCanceled(scope: CoroutineScope): Boolean { + return scope.coroutineContext[Job]?.isActive != true + } + + @Suppress("UnusedParameter") + private fun handleException(coroutineContext: CoroutineContext, throwable: Throwable?) { + println("!!! CoroutinesStore error: ${throwable?.message}") + throwable?.printStackTrace() +// internalRouter.openErrorsScreen( +// title = resourceManager.getString(R.string.common_error_general_title), +// message = resourceManager.getString(R.string.common_error_network) +// ) + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt index de3a8ac736..b604bb818c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt @@ -7,20 +7,40 @@ import android.os.Bundle import android.view.KeyEvent import android.widget.FrameLayout import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.liquiditypools.navigation.NavAction +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class AllPoolsFragment : BaseComposeBottomSheetDialogFragment() { @@ -65,9 +85,92 @@ class AllPoolsFragment : BaseComposeBottomSheetDialogFragment } ) - AllPoolsNavRoot( - viewModel = viewModel, - ) + LaunchedEffect(Unit) { + viewModel.navGraphRoutesFlow.onEach { + navController.navigate(it.routeName) + }.launchIn(this) + + viewModel.navGraphActionsFlow.onEach { + when (it) { + is NavAction.BackPressed -> { + val isBackNavigationSuccess = navController.popBackStack() + + val currentRoute = navController.currentDestination?.route + val loadingRoute = LiquidityPoolsNavGraphRoute.Loading.routeName + + if (currentRoute == loadingRoute || !isBackNavigationSuccess) { + viewModel.exitFlow() + } + } + is NavAction.ShowError -> + showErrorDialog( + title = it.errorTitle ?: resources.getString(jp.co.soramitsu.common.R.string.common_error_general_title), + message = it.errorText + ) + } + }.launchIn(this) + } + + NavHost( + startDestination = LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName, + contentAlignment = Alignment.TopCenter, + navController = navController, + modifier = Modifier + .fillMaxSize(), + ) { + + composable(LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName) { + val allPoolsScreenState by viewModel.state.collectAsState() + BottomSheetScreen { + AllPoolsScreen( + state = allPoolsScreenState, + callback = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName) { + val poolListState by viewModel.poolListState.collectAsState() + BottomSheetScreen { + PoolListScreen( + state = poolListState, + callback = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName) { + val poolDetailState by viewModel.poolDetailState.collectAsState() + BottomSheetScreen { + PoolDetailsScreen( + state = poolDetailState, + callbacks = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.LiquidityAddScreen.routeName) { + val liquidityAddState by viewModel.liquidityAddScreenState.collectAsState() + BottomSheetScreen { + LiquidityAddScreen(liquidityAddState, viewModel) + } + } + + composable(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen.routeName) { + val liquidityAddConfirmState by viewModel.liquidityAddConfirmState.collectAsStateWithLifecycle() + BottomSheetScreen { + LiquidityAddConfirmScreen(liquidityAddConfirmState, viewModel) + } + } + + composable(LiquidityPoolsNavGraphRoute.Loading.routeName) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { CircularProgressIndicator() } + } + + } } @Composable diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 32eaaef7a8..5d241536fa 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools -import android.graphics.Paint.Align import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -13,7 +12,6 @@ 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.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -28,18 +26,14 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import jp.co.soramitsu.androidfoundation.compose.sharedViewModel import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.R.drawable import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder @@ -47,18 +41,16 @@ import jp.co.soramitsu.common.compose.component.BottomSheetScreen import jp.co.soramitsu.common.compose.component.Image import jp.co.soramitsu.common.compose.component.NavigationIconButton import jp.co.soramitsu.common.compose.theme.customTypography -import jp.co.soramitsu.common.compose.theme.red -import jp.co.soramitsu.common.compose.theme.transparent import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 -import jp.co.soramitsu.common.utils.clickableWithNoIndication import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.ui_core.resources.Dimens -import jp.co.soramitsu.ui_core.theme.customColors import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -74,85 +66,6 @@ interface AllPoolsScreenInterface { } - -@Composable -fun AllPoolsNavRoot( - viewModel: AllPoolsViewModel, -) { - val navController = rememberNavController() - LaunchedEffect(Unit) { - viewModel.navGraphRoutesFlow.onEach { - println("!!! LaunchedEffect(Unit) { viewModel.navGraphRoutesFlow.onEach = ${it.routeName}") - navController.navigate(it.routeName) - }.launchIn(this) - - viewModel.navGraphActionsFlow.onEach { - println("!!! LaunchedEffect(Unit) { viewModel.navGraphActionsFlow.onEach = $it") - - when (it) { - is NavAction.BackPressed -> { - val isBackNavigationSuccess = navController.popBackStack() - - val currentRoute = navController.currentDestination?.route - val loadingRoute = LiquidityPoolsNavGraphRoute.Loading.routeName - - if (currentRoute == loadingRoute || !isBackNavigationSuccess) { - viewModel.exitFlow() - } - } - } - }.launchIn(this) - } - - NavHost( - startDestination = LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName, - contentAlignment = Alignment.TopCenter, - navController = navController, - modifier = Modifier -// .padding(padding) - .fillMaxSize(), - ) { - - composable(LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName) { - val allPoolsScreenState by viewModel.state.collectAsState() - BottomSheetScreen { - AllPoolsScreen( - state = allPoolsScreenState, - callback = viewModel - ) - } - } - - composable(LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName) { - val poolListState by viewModel.poolListState.collectAsState() - BottomSheetScreen { - PoolListScreen( - state = poolListState, - callback = viewModel - ) - } - } - - composable(LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName) { - val poolDetailState by viewModel.poolDetailState.collectAsState() - BottomSheetScreen { - PoolDetailsScreen ( - state = poolDetailState, - callbacks = viewModel - ) - } - } - - composable(LiquidityPoolsNavGraphRoute.Loading.routeName) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { CircularProgressIndicator() } - } - - } -} - @Composable fun AllPoolsScreen( state: AllPoolsState, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt index 5bb29dc1ec..b1f9742614 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt @@ -12,6 +12,13 @@ import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddState +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmState import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsCallbacks import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsState import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreenInterface @@ -33,8 +40,10 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.lastOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -45,16 +54,22 @@ import kotlinx.coroutines.launch @HiltViewModel class AllPoolsViewModel @Inject constructor( private val poolsInteractor: PoolsInteractor, -// private val coroutinesStore: CoroutinesStore, + private val coroutinesStore: CoroutinesStore, private val poolsRouter: LiquidityPoolsRouter, private val innerPoolsRouter: InternalPoolsRouter, private val accountInteractor: AccountInteractor, -) : BaseViewModel(), AllPoolsScreenInterface, PoolListScreenInterface, PoolDetailsCallbacks { + private val liquidityAddPresenter: LiquidityAddPresenter, + liquidityAddConfirmPresenter: LiquidityAddConfirmPresenter, +) : BaseViewModel(), AllPoolsScreenInterface, PoolListScreenInterface, PoolDetailsCallbacks, + LiquidityAddCallbacks by liquidityAddPresenter, + LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter +{ + val navGraphRoutesFlow: StateFlow = innerPoolsRouter.createNavGraphRoutesFlow().stateIn( scope = CoroutineScope(Dispatchers.Main.immediate), started = SharingStarted.Eagerly, - initialValue = LiquidityPoolsNavGraphRoute.AllPoolsScreen + initialValue = LiquidityPoolsNavGraphRoute.Loading ) val navGraphActionsFlow: SharedFlow = innerPoolsRouter.createNavGraphActionsFlow().shareIn( @@ -62,6 +77,11 @@ class AllPoolsViewModel @Inject constructor( started = SharingStarted.Eagerly, replay = 1 ) + val liquidityAddScreenState: StateFlow = + liquidityAddPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + val liquidityAddConfirmState: StateFlow = + liquidityAddConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) private val enteredAssetQueryFlow = MutableStateFlow("") @@ -91,6 +111,14 @@ class AllPoolsViewModel @Inject constructor( init { subscribeScreenState() + launch { + poolsInteractor.updateApy() + } + innerPoolsRouter.openAllPoolsScreen() + + liquidityAddConfirmState.onEach { + println("!!! flow liquidityAddConfirmState: $it") + } } private fun Asset.isMatchFilter(filter: String): Boolean = @@ -105,10 +133,7 @@ class AllPoolsViewModel @Inject constructor( }.launchIn(this) poolDetailsScreenArgsFlow.onEach { - println("!!! AllPoolsViewModel subscribeScreenState poolDetailsScreenArgsFlow it.ids = ${it.ids}") requestPoolDetails(it.ids)?.let { - println("!!! AllPoolsViewModel subscribeScreenState requestPoolDetails got state = $it") - _poolDetailState.value = it } }.launchIn(this) @@ -121,7 +146,7 @@ class AllPoolsViewModel @Inject constructor( val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") - println("!!! ALVM requestPoolDetails for $ids") + val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { PoolDetailsState( originTokenIcon = GradientIconData(baseAsset.iconUrl, null), @@ -132,37 +157,23 @@ class AllPoolsViewModel @Inject constructor( apy = null ) } -// val reter_old = poolsInteractor.getPoolCacheOfCurAccount(ids.first, ids.second)?.let { -// PoolDetailsState( -// originTokenIcon = GradientIconData(it.basic.baseToken.token.configuration.iconUrl, null), -// destinationTokenIcon = GradientIconData(it.basic.targetToken?.token?.configuration?.iconUrl, null), -// fromTokenSymbol = it.basic.baseToken.token.configuration.symbol, -// toTokenSymbol = it.basic.targetToken?.token?.configuration?.symbol, -// tvl = null, -// apy = null -// ) -// } return retur } override fun onPoolClicked(pair: StringPair) { - println("!!! CLIKED ON PoolPair: $pair") - innerPoolsRouter.openDetailsPoolScreen(pair) + val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + innerPoolsRouter.openDetailsPoolScreen(xorPswap) } override fun onNavigationClick() { - println("!!! CLIKED onNavigationClick") innerPoolsRouter.back() -// exitFlow() } + override fun onCloseClick() { - println("!!! CLIKED onCloseClick") innerPoolsRouter.back() -// exitFlow() } override fun onMoreClick() { - println("!!! CLIKED onMoreClick") innerPoolsRouter.openPoolListScreen() } @@ -179,7 +190,13 @@ class AllPoolsViewModel @Inject constructor( } override fun onSupplyLiquidityClick() { - println("!!! onSupplyLiquidityClick") + launch { + poolDetailsScreenArgsFlow.map { + it.ids + }.firstOrNull()?.let { ids -> + innerPoolsRouter.openAddLiquidityScreen(ids) + } + } } override fun onRemoveLiquidityClick() { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt new file mode 100644 index 0000000000..07a691e18e --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -0,0 +1,484 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd + +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address +import jp.co.soramitsu.androidfoundation.format.isZero +import jp.co.soramitsu.common.base.errors.ValidationException +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatCryptoDetail +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.formatPercent +import jp.co.soramitsu.common.utils.moreThanZero +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.common.utils.requireValue +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateAddLiquidityUseCase +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.data.LiquidityData +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto +import jp.co.soramitsu.polkaswap.api.models.WithDesired +import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.wallet.api.domain.fromValidationResult +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks +import kotlin.math.min +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +class LiquidityAddPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, + private val resourceManager: ResourceManager, + private val validateAddLiquidityUseCase: ValidateAddLiquidityUseCase, +) : LiquidityAddCallbacks { + private val enteredFromAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredToAmountFlow = MutableStateFlow(BigDecimal.ZERO) + + private var amountFrom: BigDecimal = BigDecimal.ZERO + private var amountTo: BigDecimal = BigDecimal.ZERO + + private val isFromAmountFocused = MutableStateFlow(false) + private val isToAmountFocused = MutableStateFlow(false) + + private var desired: WithDesired = WithDesired.INPUT + + private val _stateSlippage = MutableStateFlow(0.5) + val stateSlippage = _stateSlippage.asStateFlow() + + private var liquidityData: LiquidityData = LiquidityData() + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + @OptIn(ExperimentalCoroutinesApi::class) + val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> + val ids = screenArgs.ids + println("!!! assetsInPoolFlow ids = $ids") + val assetsFlow = walletInteractor.assetsFlow().mapNotNull { + val firstInPair = it.firstOrNull { + it.asset.token.configuration.id == ids.first + && it.asset.token.configuration.chainId == soraMainChainId + } + val secondInPair = it.firstOrNull { + it.asset.token.configuration.id == ids.second + && it.asset.token.configuration.chainId == soraMainChainId + } + + println("!!! assetsInPoolFlow result% $firstInPair; $secondInPair") + if (firstInPair == null || secondInPair == null) { + return@mapNotNull null + } else { + firstInPair to secondInPair + } + } + assetsFlow + }.distinctUntilChanged() + + val tokensInPoolFlow = assetsInPoolFlow.map { + it.first.asset.token.configuration to it.second.asset.token.configuration + }.distinctUntilChanged() + + val userPoolDataFlow = flowOf { getPoolDataDto() } + + init { + } + + @OptIn(FlowPreview::class) + private fun subscribeState(coroutineScope: CoroutineScope) { +// networkFeeFlow.launchIn(coroutineScope) + + userPoolDataFlow.onEach { pool -> + val apy = pool?.reservesAccount?.let { poolsInteractor.getPoolStrategicBonusAPY(it) } + stateFlow.value = stateFlow.value.copy( + apy = apy?.toBigDecimal()?.formatPercent()?.let { "$it%" } + ) + }.launchIn(coroutineScope) + + assetsInPoolFlow.onEach { (assetFrom, assetTo) -> + val totalFromCrypto = assetFrom.asset.total?.formatCrypto(assetFrom.asset.token.configuration.symbol).orEmpty() + val totalFromFiat = assetFrom.asset.fiatAmount?.formatFiat(assetFrom.asset.token.fiatSymbol) + val argsFrom = totalFromCrypto + totalFromFiat?.let { " ($it)" } + val totalFromBalance = resourceManager.getString(R.string.common_available_format, argsFrom) + + val totalToCrypto = assetTo.asset.total?.formatCrypto(assetTo.asset.token.configuration.symbol).orEmpty() + val totalToFiat = assetTo.asset.fiatAmount?.formatFiat(assetTo.asset.token.fiatSymbol) + val argsTo = totalToCrypto + totalToFiat?.let { " ($it)" } + val totalToBalance = resourceManager.getString(R.string.common_available_format, argsTo) + + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + tokenName = assetFrom.asset.token.configuration.symbol, + tokenImage = assetFrom.asset.token.configuration.iconUrl, + totalBalance = totalFromBalance, + ), + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + tokenName = assetTo.asset.token.configuration.symbol, + tokenImage = assetTo.asset.token.configuration.iconUrl, + totalBalance = totalToBalance, + ) + ) + }.launchIn(coroutineScope) + + enteredFromAmountFlow.onEach { + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatSymbol), + tokenAmount = it, + ) + ) + } + .debounce(900) + .onEach { amount -> + amountFrom = amount + desired = WithDesired.INPUT + updateAmounts() + }.launchIn(coroutineScope) + + enteredToAmountFlow.onEach { + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatSymbol), + tokenAmount = it + ), + ) + } + .debounce(900) + .onEach { amount -> + amountTo = amount + desired = WithDesired.OUTPUT + updateAmounts() + }.launchIn(coroutineScope) + + isFromAmountFocused.onEach { + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + isFocused = it + ), + ) + }.launchIn(coroutineScope) + + isToAmountFocused.onEach { + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + isFocused = it + ), + ) + }.launchIn(coroutineScope) + + stateSlippage.onEach { + stateFlow.value = stateFlow.value.copy( + slippage = "$it%" + ) + }.launchIn(coroutineScope) + + feeInfoViewStateFlow.onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it + ) + updateButtonState() + }.launchIn(coroutineScope) + } + + + private val stateFlow = MutableStateFlow(LiquidityAddState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + suspend fun getPoolDataDto(): PoolDataDto? { + val chain = accountInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(chain) + val assets = assetsInPoolFlow.firstOrNull() + val baseTokenId = assets?.first?.asset?.token?.configuration?.currencyId + val tokenToId = assets?.second?.asset?.token?.configuration?.currencyId?.fromHex() + + if (address == null || baseTokenId == null || tokenToId == null) return null + + return poolsInteractor.getUserPoolData(address, baseTokenId, tokenToId) + } + + private suspend fun updateAmounts() { + calculateAmount()?.let { targetAmount -> + val scaledTargetAmount = when { + targetAmount.isZero() -> BigDecimal.ZERO + else -> targetAmount.setScale( + min(MAX_DECIMALS_8, targetAmount.scale()), + RoundingMode.DOWN + ) + } + + if (desired == WithDesired.INPUT) { + val tokenTo = assetsInPoolFlow.firstOrNull()?.second?.asset?.token + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + tokenAmount = scaledTargetAmount, + fiatAmount = scaledTargetAmount.applyFiatRate(tokenTo?.fiatRate)?.formatFiat(tokenTo?.fiatSymbol), + + ) + ) + amountTo = scaledTargetAmount + } else { + val tokenFrom = assetsInPoolFlow.firstOrNull()?.first?.asset?.token + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + tokenAmount = scaledTargetAmount, + fiatAmount = scaledTargetAmount.applyFiatRate(tokenFrom?.fiatRate)?.formatFiat(tokenFrom?.fiatSymbol), + ) + ) + amountFrom = scaledTargetAmount + } + } + + updateButtonState() + } + + private fun updateButtonState() { + val isButtonEnabled = amountTo.moreThanZero() && amountTo.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null + println("!!! updateButtonState: $amountTo; $amountTo; ${stateFlow.value.feeInfo.feeAmount}") + println("!!! updateButtonState: isButtonEnabled = $isButtonEnabled") + stateFlow.value = stateFlow.value.copy( + buttonEnabled = isButtonEnabled + ) + } + + suspend fun calculateAmount(): BigDecimal? { + val assets = assetsInPoolFlow.firstOrNull() + + val baseAmount = if (desired == WithDesired.INPUT) enteredFromAmountFlow.value else enteredToAmountFlow.value + val targetAmount = if (desired == WithDesired.INPUT) enteredToAmountFlow.value else enteredFromAmountFlow.value + + val liquidity = userPoolDataFlow.firstOrNull() + return assets?.let { (baseAsset, targetAsset) -> + val reservesFirst = baseAsset.asset.token.configuration.amountFromPlanks(liquidity?.reservesFirst.orZero()) + val reservesSecond = targetAsset.asset.token.configuration.amountFromPlanks(liquidity?.reservesSecond.orZero()) + + if (reservesSecond.isZero() || reservesSecond.isZero()) { + targetAmount + } else { + PolkaswapFormulas.calculateAddLiquidityAmount( + baseAmount = baseAmount, + reservesFirst = reservesFirst, + reservesSecond = reservesSecond, + precisionFirst = baseAsset.asset.token.configuration.precision, + precisionSecond = targetAsset.asset.token.configuration.precision, + desired = desired + ) + } + } + } + + val isPoolPairEnabled = combine( + flowOf { + val chain = accountInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(chain)!! + address + }, + tokensInPoolFlow + ) { address, tokens -> + poolsInteractor.isPairEnabled( + tokens.first.currencyId!!, + tokens.second.currencyId!!, + accountAddress = address + ) + } + + val networkFeeFlow = combine( + enteredFromAmountFlow, + enteredToAmountFlow, + tokensInPoolFlow, + stateSlippage, + isPoolPairEnabled + ) + { amountFrom, amountTo, (baseAsset, targetAsset), slippage, pairEnabled -> + val networkFee = getLiquidityNetworkFee( + baseAsset, + targetAsset, + tokenFromAmount = amountFrom, + tokenToAmount = amountTo, + pairEnabled = pairEnabled, + pairPresented = true, //pairPresented, + slippageTolerance = slippage + ) + println("!!!! networkFeeFlow emit $networkFee") + networkFee + } + + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = flowOf { + requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(soraMainChainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + + private suspend fun getLiquidityNetworkFee( + tokenFrom: Asset, + tokenTo: Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal { + val soraChain = walletInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() + val result = poolsInteractor.calcAddLiquidityNetworkFee( + address, + tokenFrom, + tokenTo, + tokenFromAmount, + tokenToAmount, + pairEnabled, + pairPresented, + slippageTolerance, + ) + return result ?: BigDecimal.ZERO + } + + override fun onNavigationClick() { + internalPoolsRouter.back() + } + + private fun setButtonLoading(loading: Boolean) { + stateFlow.value = stateFlow.value.copy( + buttonLoading = loading + ) + } + + override fun onReviewClick() { + setButtonLoading(true) + println("!!! should setButtonLoading(true)") + + coroutinesStore.uiScope.launch { + val utilityAssetId = requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) + val utilityAmount = walletInteractor.getCurrentAsset(soraMainChainId, utilityAssetId).total + val feeAmount = networkFeeFlow.firstOrNull().orZero() + + val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch + + val validationResult = validateAddLiquidityUseCase( + assetFrom = poolAssets.first, + assetTo = poolAssets.second, + utilityAssetId = utilityAssetId, + utilityAmount = utilityAmount.orZero(), + amountFrom = amountFrom, + amountTo = amountTo, + feeAmount = feeAmount, + ) + + validationResult.exceptionOrNull()?.let { + showError(it) + return@launch + } + + val validationValue = validationResult.requireValue() + ValidationException.fromValidationResult(validationValue, resourceManager)?.let { + showError(it) + return@launch + } + + val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch + internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountFrom, amountTo, stateFlow.value.apy.orEmpty()) + }.invokeOnCompletion { + println("!!! setButtonLoading(false)") + coroutinesStore.uiScope.launch { + delay(300) + setButtonLoading(false) + } + } + } + + override fun onFromAmountChange(amount: BigDecimal) { + enteredFromAmountFlow.value = amount + amountFrom = amount + + updateButtonState() + } + + override fun onToAmountChange(amount: BigDecimal) { + enteredToAmountFlow.value = amount + amountTo = amount + + updateButtonState() + } + + override fun onFromAmountFocusChange(isFocused: Boolean) { + isFromAmountFocused.value = isFocused + if (desired != WithDesired.INPUT) { + desired = WithDesired.INPUT + } + } + + override fun onToAmountFocusChange(isFocused: Boolean) { + isToAmountFocused.value = isFocused + if (desired != WithDesired.OUTPUT) { + desired = WithDesired.OUTPUT + } + } + + private fun showError(throwable: Throwable) { + when (throwable) { + is ValidationException -> { + val (title, text) = throwable + internalPoolsRouter.openErrorsScreen(title, text) + } + + else -> { + throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } + } + } + } +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt new file mode 100644 index 0000000000..7abdb6220e --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -0,0 +1,254 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import java.math.BigDecimal +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.AmountInput +import jp.co.soramitsu.common.compose.component.AmountInputViewState +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.DoubleGradientIcon +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.compose.component.GradientIconState +import jp.co.soramitsu.common.compose.component.H4Bold +import jp.co.soramitsu.common.compose.component.InfoTable +import jp.co.soramitsu.common.compose.component.InfoTableItem +import jp.co.soramitsu.common.compose.component.InfoTableItemAsset +import jp.co.soramitsu.common.compose.component.MarginHorizontal +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.NavigationIconButton +import jp.co.soramitsu.common.compose.component.TitleIconValueState +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.theme.alertYellow +import jp.co.soramitsu.common.compose.theme.backgroundBlack +import jp.co.soramitsu.common.compose.theme.colorAccentDark +import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.common.compose.theme.grayButtonBackground +import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.compose.theme.white08 +import jp.co.soramitsu.feature_wallet_impl.R + +data class LiquidityAddState( + val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val toAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val slippage: String = "0.5%", + val apy: String? = null, + val feeInfo: FeeInfoViewState = FeeInfoViewState.default, + val buttonEnabled: Boolean = false, + val buttonLoading: Boolean = false +) + +interface LiquidityAddCallbacks { + + fun onNavigationClick() + + fun onReviewClick() + + fun onFromAmountChange(amount: BigDecimal) + + fun onFromAmountFocusChange(isFocused: Boolean) + + fun onToAmountChange(amount: BigDecimal) + + fun onToAmountFocusChange(isFocused: Boolean) +} + +@Composable +fun LiquidityAddScreen( + state: LiquidityAddState, + callbacks: LiquidityAddCallbacks +) { + val keyboardController = LocalSoftwareKeyboardController.current + val runCallback: (() -> Unit) -> Unit = { block -> + keyboardController?.hide() + block() + } + + Column( + modifier = Modifier + .background(backgroundBlack) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Toolbar(callbacks) + + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MarginVertical(margin = 16.dp) + + Box( + modifier = Modifier + .padding(top = 8.dp), + contentAlignment = Alignment.Center + ) { + Column { + AmountInput( + state = state.fromAmountInputViewState, + borderColorFocused = colorAccentDark, + onInput = callbacks::onFromAmountChange, + onInputFocusChange = callbacks::onFromAmountFocusChange, + onKeyboardDone = { keyboardController?.hide() } + ) + + MarginVertical(margin = 8.dp) + + AmountInput( + state = state.toAmountInputViewState, + borderColorFocused = colorAccentDark, + onInput = callbacks::onToAmountChange, + onInputFocusChange = callbacks::onToAmountFocusChange, + onKeyboardDone = { keyboardController?.hide() } + ) + } + + Icon( + modifier = Modifier + .clip(CircleShape) + .background(grayButtonBackground) + .border(width = 1.dp, color = white08, shape = CircleShape) + .padding(8.dp), + painter = painterResource(R.drawable.ic_plus_white_24), + contentDescription = null, + tint = white + ) + } + + MarginVertical(margin = 24.dp) + + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + ) { + Column { + InfoTableItem(TitleValueViewState("Slippage", state.slippage)) + InfoTableItem( + TitleValueViewState( + title = "Strategic bonus APY", + value = state.apy, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) + ) + ) + InfoTableItemAsset( + TitleIconValueState( + title = "Rewards payout in", + iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", + value = "PSWAP" + ) + ) + InfoTableItem( + TitleValueViewState( + title = "Network fee", + value = state.feeInfo.feeAmount, + additionalValue = state.feeInfo.feeAmountFiat, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) + ) + ) + } + } + + MarginVertical(margin = 24.dp) + } + + AccentButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Review", + enabled = state.buttonEnabled, + loading = state.buttonLoading, + onClick = callbacks::onReviewClick + ) + + MarginVertical(margin = 8.dp) + } +} + +@Composable +private fun Toolbar(callback: LiquidityAddCallbacks) { + Row( + modifier = Modifier + .wrapContentHeight() + .padding(bottom = 12.dp) + ) { + NavigationIconButton( + modifier = Modifier.padding(start = 16.dp), + onNavigationClick = callback::onNavigationClick + ) + + H4Bold( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + text = "Supply liquidity", + textAlign = TextAlign.Center + ) + + NavigationIconButton( + modifier = Modifier + .align(Alignment.Top) + .padding(end = 16.dp), + navigationIconResId = R.drawable.ic_cross_32, + onNavigationClick = callback::onNavigationClick + ) + } +} + +@Preview +@Composable +private fun PreviewLiquidityAddScreen() { + BottomSheetScreen { + LiquidityAddScreen( + state = LiquidityAddState( + fromAmountInputViewState = AmountInputViewState.defaultObj, + toAmountInputViewState = AmountInputViewState.defaultObj, + apy = "23.3%", + feeInfo = FeeInfoViewState.default, + slippage = "0.5%" + ), + callbacks = object : LiquidityAddCallbacks { + override fun onNavigationClick() {} + override fun onReviewClick() {} + override fun onFromAmountChange(amount: BigDecimal) {} + override fun onFromAmountFocusChange(isFocused: Boolean) {} + override fun onToAmountChange(amount: BigDecimal) {} + override fun onToAmountFocusChange(isFocused: Boolean) {} + }, + ) + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt new file mode 100644 index 0000000000..9af82de551 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -0,0 +1,237 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm + +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatCryptoDetail +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +class LiquidityAddConfirmPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, +) : LiquidityAddConfirmCallbacks { + + private val _stateSlippage = MutableStateFlow(0.5) + val stateSlippage = _stateSlippage.asStateFlow() + + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> + val ids = screenArgs.ids + val assetsFlow = walletInteractor.assetsFlow().mapNotNull { + val firstInPair = it.firstOrNull { + it.asset.token.configuration.id == ids.first + && it.asset.token.configuration.chainId == soraMainChainId + } + val secondInPair = it.firstOrNull { + it.asset.token.configuration.id == ids.second + && it.asset.token.configuration.chainId == soraMainChainId + } + if (firstInPair == null || secondInPair == null) { + return@mapNotNull null + } else { + firstInPair to secondInPair + } + } + assetsFlow + } + + val tokensInPoolFlow = assetsInPoolFlow.map { + it.first.asset.token to it.second.asset.token + }.distinctUntilChanged() + + val isPoolPairEnabled = combine( + flowOf { + val chain = accountInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(chain)!! + address + }, + tokensInPoolFlow + ) { address, tokens -> + poolsInteractor.isPairEnabled( + tokens.first.configuration.currencyId!!, + tokens.second.configuration.currencyId!!, + accountAddress = address + ) + } + + init { + + } + private val stateFlow = MutableStateFlow(LiquidityAddConfirmState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetFrom, assetTo) -> + stateFlow.value = stateFlow.value.copy( + assetFrom = assetFrom.configuration, + assetTo = assetTo.configuration, + baseAmount = screenArgs.amountFrom.formatCrypto(assetFrom.configuration.symbol), + baseFiat = screenArgs.amountFrom.applyFiatRate(assetFrom.fiatRate)?.formatFiat(assetFrom.fiatSymbol).orEmpty(), + targetAmount = screenArgs.amountTo.formatCrypto(assetTo.configuration.symbol), + targetFiat = screenArgs.amountTo.applyFiatRate(assetTo.fiatRate)?.formatFiat(assetTo.fiatSymbol).orEmpty(), + apy = screenArgs.apy + ) + }.launchIn(coroutineScope) + + stateSlippage.onEach { + stateFlow.value = stateFlow.value.copy( + slippage = "$it%" + ) + }.launchIn(coroutineScope) + + createFeeInfoViewState().onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it + ) + }.launchIn(coroutineScope) + + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun createFeeInfoViewState(): Flow { + val networkFeeHelperFlow = combine( + screenArgsFlow, + tokensInPoolFlow, + stateSlippage, + isPoolPairEnabled + ) + { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> + val networkFee = getLiquidityNetworkFee( + baseAsset.configuration, + targetAsset.configuration, + tokenFromAmount = screenArgs.amountFrom, + tokenToAmount = screenArgs.amountTo, + pairEnabled = pairEnabled, + pairPresented = true, //pairPresented, + slippageTolerance = slippage + ) + networkFee + } + + return flowOf { + requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + combine( + networkFeeHelperFlow, + walletInteractor.assetFlow(soraMainChainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + return@combine FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + } + + + + private suspend fun getLiquidityNetworkFee( + tokenFrom: Asset, + tokenTo: Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal { + val soraChain = walletInteractor.getChain(soraMainChainId) + val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() + val result = poolsInteractor.calcAddLiquidityNetworkFee( + user, + tokenFrom, + tokenTo, + tokenFromAmount, + tokenToAmount, + pairEnabled, + pairPresented, + slippageTolerance, + ) + return result ?: BigDecimal.ZERO + } + + override fun onNavigationClick() { + internalPoolsRouter.back() + } + + override fun onConfirmClick() { + println("!!! LiquidityAddConfirm onConfirmClick") + coroutinesStore.uiScope.launch { + val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch + val tokento = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch + val amountFrom = screenArgsFlow.firstOrNull()?.amountFrom.orZero() + val amountTo = screenArgsFlow.firstOrNull()?.amountTo.orZero() + val pairEnabled = isPoolPairEnabled.firstOrNull() ?: true + val pairPresented = true + var result = "" + try { + println("!!! LiquidityAddConfirm onConfirmClick run observeAddLiquidity") + result = poolsInteractor.observeAddLiquidity( + tokenFrom, + tokento, + amountFrom, + amountTo, + pairEnabled, + pairPresented, + _stateSlippage.value, + ) + } catch (t: Throwable) { + internalPoolsRouter.openErrorsScreen(message = t.message.orEmpty()) + } + + if (result.isNotEmpty()) { + println("!!! LiquidityAddConfirm onConfirmClick result = $result") + // TODO ALL DONE SCREEN + } + } + } + +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt new file mode 100644 index 0000000000..710039d328 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -0,0 +1,267 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm + +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import coil.compose.AsyncImage +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.B1 +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder +import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.compose.component.H4Bold +import jp.co.soramitsu.common.compose.component.InfoTableItem +import jp.co.soramitsu.common.compose.component.InfoTableItemAsset +import jp.co.soramitsu.common.compose.component.MarginHorizontal +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.NavigationIconButton +import jp.co.soramitsu.common.compose.component.TitleIconValueState +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.component.getImageRequest +import jp.co.soramitsu.common.compose.theme.backgroundBlack +import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.common.compose.theme.white50 +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.feature_wallet_impl.R + +data class LiquidityAddConfirmState( + val assetFrom: Asset? = null, + val assetTo: Asset? = null, + val baseAmount: String = "", + val baseFiat: String = "", + val targetAmount: String = "", + val targetFiat: String = "", + val slippage: String = "0.5%", + val apy: String? = null, + val feeInfo: FeeInfoViewState = FeeInfoViewState.default, +) + +interface LiquidityAddConfirmCallbacks { + + fun onNavigationClick() + + fun onConfirmClick() +} + +@Composable +fun LiquidityAddConfirmScreen( + state: LiquidityAddConfirmState, + callbacks: LiquidityAddConfirmCallbacks +) { + Column( + modifier = Modifier + .background(backgroundBlack) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Toolbar(callbacks) + + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MarginVertical(margin = 16.dp) + + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetFrom?.iconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = 7.dp) + .zIndex(1f) + ) + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetTo?.iconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = (-7).dp) + .zIndex(0f) + ) + } + + Row( + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = state.assetFrom?.symbol?.uppercase().orEmpty(), + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End + ) + + MarginHorizontal(margin = 8.dp) + + Icon( + painter = painterResource(jp.co.soramitsu.common.R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + + MarginHorizontal(margin = 8.dp) + + Text( + text = state.assetTo?.symbol?.uppercase().orEmpty(), + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start + ) + } + + MarginVertical(margin = 8.dp) + B1( + modifier = Modifier + .padding(horizontal = 7.dp) + .align(Alignment.CenterHorizontally), + text = "Output is estimated. If the price changes more than 0.5% your transaction will revert.", + textAlign = TextAlign.Center, + color = white50 + ) + MarginVertical(margin = 8.dp) + + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + ) { + Column { + MarginVertical(margin = 6.dp) + InfoTableItem(TitleValueViewState("Slippage", state.slippage)) + InfoTableItem( + TitleValueViewState( + title = "Strategic bonus APY", + value = state.apy, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) + ) + ) + InfoTableItemAsset( + TitleIconValueState( + title = "Rewards payout in", + iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", + value = "PSWAP" + ) + ) + InfoTableItem( + TitleValueViewState( + title = "Network fee", + value = state.feeInfo.feeAmount, + additionalValue = state.feeInfo.feeAmountFiat, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) + ) + ) + InfoTableItem( + TitleValueViewState( + title = stringResource(id = R.string.pl_your_pooled_format, state.assetFrom?.symbol?.uppercase().orEmpty()), + value = state.baseAmount, + additionalValue = state.baseFiat + ) + ) + InfoTableItem( + TitleValueViewState( + title = stringResource(id = R.string.pl_your_pooled_format, state.assetTo?.symbol?.uppercase().orEmpty()), + value = state.targetAmount, + additionalValue = state.targetFiat + ) + ) + MarginVertical(margin = 8.dp) + } + } + + MarginVertical(margin = 24.dp) + } + + AccentButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Confirm", + onClick = callbacks::onConfirmClick + ) + + MarginVertical(margin = 8.dp) + } +} + +@Composable +private fun Toolbar(callback: LiquidityAddConfirmCallbacks) { + Row( + modifier = Modifier + .wrapContentHeight() + .padding(bottom = 12.dp) + ) { + NavigationIconButton( + modifier = Modifier.padding(start = 16.dp), + onNavigationClick = callback::onNavigationClick + ) + + H4Bold( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + text = "Confirm liquidity", + textAlign = TextAlign.Center + ) + + NavigationIconButton( + modifier = Modifier + .align(Alignment.Top) + .padding(end = 16.dp), + navigationIconResId = R.drawable.ic_cross_32, + onNavigationClick = callback::onNavigationClick + ) + } +} + +@Preview +@Composable +private fun PreviewLiquidityAddConfirmScreen() { + BottomSheetScreen { + LiquidityAddConfirmScreen( + state = LiquidityAddConfirmState( + slippage = "0.5%", + apy = "23.3%", + feeInfo = FeeInfoViewState.default, + ), + callbacks = object : LiquidityAddConfirmCallbacks { + override fun onNavigationClick() {} + override fun onConfirmClick() {} + }, + ) + } +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt new file mode 100644 index 0000000000..c123026c26 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -0,0 +1,54 @@ +package jp.co.soramitsu.liquiditypools.impl.usecase + +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.wallet.api.domain.TransferValidationResult +import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus + +class ValidateAddLiquidityUseCase @Inject constructor() { + + operator fun invoke( + assetFrom: AssetWithStatus, + assetTo: AssetWithStatus, + utilityAssetId: String, + utilityAmount: BigDecimal, + amountFrom: BigDecimal, + amountTo: BigDecimal, + feeAmount: BigDecimal + ): Result { + return runCatching { + val isEnoughAmountFrom = amountFrom < assetFrom.asset.total.orZero() + feeAmount.takeIf { + assetFrom.asset.token.configuration.id == utilityAssetId + }.orZero() + + val isEnoughAmountTo = amountTo < assetTo.asset.total.orZero() + feeAmount.takeIf { + assetTo.asset.token.configuration.id == utilityAssetId + }.orZero() + + val isEnoughAmountFee = if (utilityAssetId in listOf(assetFrom.asset.token.configuration.id, assetTo.asset.token.configuration.id)) { + true + } else { + feeAmount < utilityAmount + } + + val validationChecks = mapOf ( + TransferValidationResult.InsufficientBalance to (!isEnoughAmountFrom || !isEnoughAmountTo), + TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee + ) + + val result = performChecks(validationChecks) + return Result.success(result) + } + } + + private fun performChecks( + checks: Map, + ): TransferValidationResult { + checks.forEach { (result, condition) -> + if (condition) return result + } + return TransferValidationResult.Valid + } +} \ No newline at end of file diff --git a/feature-polkaswap-api/build.gradle.kts b/feature-polkaswap-api/build.gradle.kts index 0fd6ae5bfe..2e616cd668 100644 --- a/feature-polkaswap-api/build.gradle.kts +++ b/feature-polkaswap-api/build.gradle.kts @@ -30,6 +30,13 @@ dependencies { implementation(projects.runtime) implementation(projects.featureWalletApi) implementation(project(mapOf("path" to ":common"))) + + implementation("javax.inject:javax.inject:1") + + implementation(libs.xnetworking.basic) + implementation(libs.xnetworking.sorawallet) { + exclude(group = "jp.co.soramitsu.xnetworking", module = "basic") + } } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt new file mode 100644 index 0000000000..f249288bbc --- /dev/null +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt @@ -0,0 +1,11 @@ +package jp.co.soramitsu.polkaswap.api.data + +import java.math.BigDecimal + +data class LiquidityData( + val firstReserves: BigDecimal = BigDecimal.ZERO, + val secondReserves: BigDecimal = BigDecimal.ZERO, + val firstPooled: BigDecimal = BigDecimal.ZERO, + val secondPooled: BigDecimal = BigDecimal.ZERO, + val sbApy: Double? = null, +) diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index 772781da97..7dcdcd5cf7 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -1,13 +1,16 @@ package jp.co.soramitsu.polkaswap.api.data +import java.math.BigDecimal import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigInteger +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair interface PolkaswapRepository { suspend fun getAvailableDexes(chainId: ChainId): List @@ -70,4 +73,32 @@ interface PolkaswapRepository { baseTokenId: String, tokenId: ByteArray ): PoolDataDto? + + suspend fun calcAddLiquidityNetworkFee( + address: String, + tokenFrom: Asset, + tokenTo: Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? + + suspend fun getPoolBaseTokenDexId(tokenId: String?): Int + suspend fun updatePoolsSbApy() + fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? + + suspend fun observeAddLiquidity( + address: String, + keypair: Keypair, + tokenFrom: Asset, + tokenTo: Asset, + amountFrom: BigDecimal, + amountTo: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): Pair? + } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt index f5590b7e7c..2257c133ef 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt @@ -63,4 +63,6 @@ interface PolkaswapInteractor { suspend fun getAvailableDexesForPair(tokenFromId: String, tokenToId: String, dexes: List): List fun observeHasReadDisclaimer(): Flow + suspend fun updatePoolsSbApy() + fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt new file mode 100644 index 0000000000..647a0348fd --- /dev/null +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt @@ -0,0 +1,31 @@ +package jp.co.soramitsu.polkaswap.api.sorablockexplorer + +import javax.inject.Inject +import javax.inject.Singleton +import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo +import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.sbapy.SbApyInfo + +@Singleton +class BlockExplorerManager @Inject constructor( + private val info: SoraWalletBlockExplorerInfo +) { + + private val tempApy = mutableListOf() + + fun getTempApy(id: String) = tempApy.find { + it.id == id + }?.sbApy?.times(100) + + suspend fun updatePoolsSbApy() { + updateSbApyInternal() + } + + private suspend fun updateSbApyInternal() { + runCatching { + val response = info.getSpApy() + tempApy.clear() + tempApy.addAll(response) + } + } + +} \ No newline at end of file diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index e0c3312201..3853e3a61e 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -12,12 +12,16 @@ import jp.co.soramitsu.common.data.network.config.PolkaswapRemoteConfig import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.dexManager +import jp.co.soramitsu.common.utils.fromHex import jp.co.soramitsu.common.utils.poolTBC import jp.co.soramitsu.common.utils.poolXYK import jp.co.soramitsu.common.utils.u32ArgumentFromStorageKey import jp.co.soramitsu.core.extrinsic.ExtrinsicService +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.polkaswap.api.data.LiquidityData import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData @@ -28,7 +32,12 @@ import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.api.models.backStrings import jp.co.soramitsu.polkaswap.api.models.toFilters import jp.co.soramitsu.polkaswap.api.models.toMarkets +import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.bindings.bindDexInfos +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.liquidityAdd +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.register import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.swap import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.ext.addressOf @@ -38,15 +47,18 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.runtime.network.subscriptionFlowCatching import jp.co.soramitsu.runtime.storage.source.StorageDataSource +import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.extensions.toHexString import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex +import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder import jp.co.soramitsu.shared_utils.runtime.metadata.module import jp.co.soramitsu.shared_utils.runtime.metadata.storage import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey import jp.co.soramitsu.shared_utils.scale.Schema +import jp.co.soramitsu.shared_utils.scale.dataType.uint32 import jp.co.soramitsu.shared_utils.scale.sizedByteArray import jp.co.soramitsu.shared_utils.scale.uint128 import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId @@ -61,7 +73,7 @@ import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequ import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository -import jp.co.soramitsu.wallet.impl.domain.model.Asset +import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll @@ -77,8 +89,8 @@ class PolkaswapRepositoryImpl @Inject constructor( private val chainRegistry: ChainRegistry, private val rpcCalls: RpcCalls, private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository - + private val walletRepository: WalletRepository, + private val blockExplorerManager: BlockExplorerManager, ) : PolkaswapRepository { override suspend fun getAvailableDexes(chainId: ChainId): List { @@ -201,6 +213,7 @@ class PolkaswapRepositoryImpl @Inject constructor( markets: List, desired: WithDesired ): BigInteger { + val chain = chainRegistry.getChain(chainId) return extrinsicService.estimateFee(chain) { swap(dexId, inputAssetId, outputAssetId, amount, limit, filter, markets, desired) @@ -328,10 +341,16 @@ class PolkaswapRepositoryImpl @Inject constructor( } } - protected fun getPoolStrategicBonusAPY( - reserveAccountOfPool: String, - ): Double? = null -// blockExplorerManager.getTempApy(reserveAccountOfPool) + override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { + val tempApy = blockExplorerManager.getTempApy(reserveAccountOfPool) + println("!!! blockExplorerManager getPoolStrategicBonusAPY for address $reserveAccountOfPool = $tempApy") + return tempApy + } + + override suspend fun updatePoolsSbApy() { + println("!!! call blockExplorerManager.updatePoolsSbApy()") + blockExplorerManager.updatePoolsSbApy() + } override suspend fun getBasicPools(): List { println("!!! getBasicPools() start") @@ -339,7 +358,7 @@ class PolkaswapRepositoryImpl @Inject constructor( val storage = runtimeOrNull?.metadata ?.module(Modules.POOL_XYK) ?.storage("Reserves") - println("!!! getBasicPools() storage = $storage") + val list = mutableListOf() val soraChain = chainRegistry.getChain(soraMainChainId) @@ -361,8 +380,6 @@ class PolkaswapRepositoryImpl @Inject constructor( println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") soraAssets.forEach { asset -> - println("!!! getBasicPools() soraAssets.forEach { asset ${asset.token.configuration.symbol}") - val currencyId = asset.token.configuration.currencyId val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } key?.let { @@ -400,7 +417,7 @@ class PolkaswapRepositoryImpl @Inject constructor( element ) // todo remove - if (list.size > 5) return list + if (list.size > 0) return list } } } @@ -442,7 +459,6 @@ class PolkaswapRepositoryImpl @Inject constructor( baseTokenId: String, tokenId: ByteArray ): PoolDataDto? { - println("!!! getUserPoolData") val reserves = getPairWithXorReserves(baseTokenId, tokenId) val totalIssuanceAndProperties = getPoolTotalIssuanceAndProperties(baseTokenId, tokenId, address) @@ -463,6 +479,80 @@ class PolkaswapRepositoryImpl @Inject constructor( ) } + override suspend fun calcAddLiquidityNetworkFee( + address: String, + tokenFrom: Asset, + tokenTo: Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? { + val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenFromAmount, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenToAmount, slippageTolerance) + val dexId = getPoolBaseTokenDexId(tokenFrom.currencyId) + val chain = chainRegistry.getChain(soraMainChainId) + val fee = extrinsicService.estimateFee(chain) { + liquidityAdd( + dexId, + tokenFrom, + tokenTo, + pairPresented, + pairEnabled, + tokenFromAmount, + tokenToAmount, + amountFromMin, + amountToMin + ) + } + + val feeToken = chain.utilityAsset + return feeToken?.amountFromPlanks(fee) + } + + override suspend fun getPoolBaseTokenDexId(tokenId: String?): Int { + return getPoolBaseTokens().first { + it.second == tokenId + }.first + } + + private suspend fun getPoolBaseTokens(): List> { + val runtimeSnapshot = chainRegistry.getRuntimeOrNull(soraMainChainId) + val metadataStorage = runtimeSnapshot?.metadata + ?.module("DEXManager") + ?.storage("DEXInfos") + val partialKey = metadataStorage + ?.storageKey() ?: error("getPoolBaseTokenDexId storageKey not supported") + + return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = StateKeys(listOf(partialKey)), + mapper = pojoList().nonNull() + ).let { storageKeys -> + storageKeys.mapNotNull { storageKey -> + chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull() + ).let { storage -> + val storageType = metadataStorage.type.value!! + val storageRawData = + storageType.fromHex(runtimeSnapshot, storage) + (storageRawData as? Struct.Instance)?.let { instance -> + instance.mapToToken("baseAssetId")?.let { token -> + storageKey.takeInt32() to token + } + } + } + } + } + } + + + fun Struct.Instance.mapToToken(field: String) = + this.get(field)?.getTokenId()?.toHexString(true) + + fun String.takeInt32() = uint32.fromHex(this.takeLast(8)).toInt() + private suspend fun getPoolTotalIssuanceAndProperties( baseTokenId: String, tokenId: ByteArray, @@ -523,11 +613,9 @@ class PolkaswapRepositoryImpl @Inject constructor( baseTokenId: String, tokenId: ByteArray ): Pair? { - println("!!! getPairWithXorReserves") val storageKey = chainRegistry.getRuntimeOrNull(soraMainChainId)?.reservesKey(baseTokenId, tokenId) ?: return null - println("!!! getPairWithXorReserves storageKey = $storageKey") return try { chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), @@ -773,6 +861,54 @@ class PolkaswapRepositoryImpl @Inject constructor( }.getOrThrow() } + override suspend fun observeAddLiquidity( + address: String, + keypair: Keypair, + tokenFrom: Asset, + tokenTo: Asset, + amountFrom: BigDecimal, + amountTo: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): Pair? { + val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountFrom, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTo, slippageTolerance) + val dexId = getPoolBaseTokenDexId(tokenFrom.currencyId) + val soraChain = accountRepository.getChain(soraMainChainId) + + val baseTokenId = tokenFrom.currencyId + val targetTokenId = tokenTo.currencyId + if (baseTokenId == null || targetTokenId == null) return null + + return extrinsicService.submitAndWatchExtrinsic(soraChain) { + if (!pairPresented) { + if (!pairEnabled) { + register( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId + ) + } + initializePool( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId + ) + } + + depositLiquidity( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId, + baseAssetAmount = mapBalance(amountFrom, tokenFrom.precision), + targetAssetAmount = mapBalance(amountTo, tokenTo.precision), + amountFromMin = mapBalance(amountFromMin, tokenFrom.precision), + amountToMin = mapBalance(amountToMin, tokenTo.precision) + ) + } + } + fun RuntimeSnapshot.accountPoolsKey(address: String): String = this.metadata.module(Modules.POOL_XYK) .storage("AccountPools") diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt index 34796577d8..a97deb8208 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt @@ -1,11 +1,15 @@ package jp.co.soramitsu.polkaswap.impl.data.network.blockchain +import java.math.BigDecimal import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.DictEnum import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder import java.math.BigInteger +import jp.co.soramitsu.common.utils.Modules +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount fun ExtrinsicBuilder.swap( dexId: Int, @@ -46,3 +50,101 @@ fun ExtrinsicBuilder.swap( ) ) ) + +fun ExtrinsicBuilder.register( + dexId: Int, + baseAssetId: String, + targetAssetId: String +) = this.call( + "TradingPair", + "register", + mapOf( + "dex_id" to dexId.toBigInteger(), + "base_asset_id" to baseAssetId.mapCodeToken(), + "target_asset_id" to targetAssetId.mapCodeToken(), + ) +) + +fun ExtrinsicBuilder.initializePool( + dexId: Int, + baseAssetId: String, + targetAssetId: String +) = this.call( + Modules.POOL_XYK, + "initialize_pool", + mapOf( + "dex_id" to dexId.toBigInteger(), + "asset_a" to baseAssetId.mapCodeToken(), + "asset_b" to targetAssetId.mapCodeToken(), + ) +) + +fun ExtrinsicBuilder.depositLiquidity( + dexId: Int, + baseAssetId: String, + targetAssetId: String, + baseAssetAmount: BigInteger, + targetAssetAmount: BigInteger, + amountFromMin: BigInteger, + amountToMin: BigInteger +) = this.call( + Modules.POOL_XYK, + "deposit_liquidity", + mapOf( + "dex_id" to dexId.toBigInteger(), + "input_asset_a" to baseAssetId.mapCodeToken(), + "input_asset_b" to targetAssetId.mapCodeToken(), + "input_a_desired" to baseAssetAmount, + "input_b_desired" to targetAssetAmount, + "input_a_min" to amountFromMin, + "input_b_min" to amountToMin + ) +) + +fun ExtrinsicBuilder.liquidityAdd( + dexId: Int, + tokenFrom: Asset, + tokenTo: Asset, + pairPresented: Boolean, + pairEnabled: Boolean, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + amountFromMin: BigDecimal, + amountToMin: BigDecimal +) { + val baseTokenId = tokenFrom.currencyId + val targetTokenId = tokenTo.currencyId + if (baseTokenId != null && targetTokenId != null) { + if (!pairPresented) { + if (!pairEnabled) { + register( + dexId = dexId, + baseTokenId, + targetTokenId + ) + } + initializePool( + dexId = dexId, + baseTokenId, + targetTokenId + ) + } + + depositLiquidity( + dexId = dexId, + baseTokenId, + targetTokenId, + tokenFrom.planksFromAmount(tokenFromAmount), + tokenTo.planksFromAmount(tokenToAmount), + tokenFrom.planksFromAmount(amountFromMin), + tokenTo.planksFromAmount(amountToMin), + ) + } +} + +fun String.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) +) + +fun String.mapAssetId() = this.fromHex().mapAssetId() +fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 98a113bb09..4ea026c2da 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -1,12 +1,16 @@ package jp.co.soramitsu.polkaswap.impl.di +import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Named import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.common.BuildConfig +import jp.co.soramitsu.common.data.network.OptionsProvider import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.common.domain.NetworkStateService @@ -22,6 +26,7 @@ import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.IChainRegistry import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao +import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.chain.ChainSyncService import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.runtime.multiNetwork.connection.ConnectionPool @@ -31,6 +36,10 @@ import jp.co.soramitsu.runtime.multiNetwork.runtime.RuntimeSubscriptionPool import jp.co.soramitsu.runtime.multiNetwork.runtime.RuntimeSyncService import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository +import jp.co.soramitsu.xnetworking.basic.networkclient.SoramitsuNetworkClient +import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo +import jp.co.soramitsu.xnetworking.sorawallet.mainconfig.SoraRemoteConfigBuilder +import jp.co.soramitsu.xnetworking.sorawallet.mainconfig.SoraRemoteConfigProvider @InstallIn(SingletonComponent::class) @Module @@ -44,7 +53,8 @@ class PolkaswapFeatureModule { chainRegistry: ChainRegistry, rpcCalls: RpcCalls, accountRepository: AccountRepository, - walletRepository: WalletRepository + walletRepository: WalletRepository, + sorablockexplorer: BlockExplorerManager ): PolkaswapRepository { return PolkaswapRepositoryImpl( remoteConfigFetcher, @@ -53,7 +63,8 @@ class PolkaswapFeatureModule { chainRegistry, rpcCalls, accountRepository, - walletRepository + walletRepository, + sorablockexplorer ) } @@ -104,4 +115,36 @@ class PolkaswapFeatureModule { assetReadOnlyCache, chainsRepository ) + + @Singleton + @Provides + fun provideSoraWalletBlockExplorerInfo( + client: SoramitsuNetworkClient, + soraRemoteConfigBuilder: SoraRemoteConfigBuilder, + ): SoraWalletBlockExplorerInfo { + return SoraWalletBlockExplorerInfo( + networkClient = client, + soraRemoteConfigBuilder = soraRemoteConfigBuilder, + ) + } + +// @Singleton +// @Provides +// fun provideSoramitsuNetworkClient(): SoramitsuNetworkClient = +// SoramitsuNetworkClient(logging = BuildConfig.DEBUG, timeout = 20000) +// + @Singleton + @Provides + fun provideSoraRemoteConfigBuilder( + client: SoramitsuNetworkClient, + @ApplicationContext context: Context, + ): SoraRemoteConfigBuilder { + return SoraRemoteConfigProvider( + context = context, + client = client, + commonUrl = OptionsProvider.soraConfigCommon, + mobileUrl = OptionsProvider.soraConfigMobile, + ).provide() + } + } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt index 6cf904310e..fa6b064e5e 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt @@ -269,6 +269,13 @@ class PolkaswapInteractorImpl @Inject constructor( }.filter { it.second }.map { it.first } } + override suspend fun updatePoolsSbApy() { + polkaswapRepository.updatePoolsSbApy() + } + + override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String) = + polkaswapRepository.getPoolStrategicBonusAPY(reserveAccountOfPool) + override suspend fun swap( dexId: Int, inputAssetId: String, diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt index 6cef5f3930..2e45012666 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt @@ -96,7 +96,7 @@ interface WalletRepository { suspend fun getStashAccount(chainId: ChainId, accountId: AccountId): AccountId? suspend fun getTotalBalance( - chainAsset: jp.co.soramitsu.core.models.Asset, + chainAsset: CoreAsset, chain: Chain, accountId: ByteArray ): BigInteger diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt index cc35a1b208..0f5638439d 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt @@ -422,7 +422,7 @@ class WalletRepositoryImpl( } override suspend fun getTotalBalance( - chainAsset: jp.co.soramitsu.core.models.Asset, + chainAsset: CoreAsset, chain: Chain, accountId: ByteArray ): BigInteger { @@ -481,7 +481,7 @@ class WalletRepositoryImpl( senderAddress: String, fee: BigDecimal, source: OperationLocal.Source, - utilityAsset: jp.co.soramitsu.core.models.Asset? + utilityAsset: CoreAsset? ) = OperationLocal.manualTransfer( hash = hash, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index ac77a5338d..f304e3f4ac 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -318,8 +318,8 @@ class BalanceListViewModel @Inject constructor( } if (it is NFTCollection.Loaded.WithFailure) { - println("!!! NFTCollection.Loaded.WithFailure: ${it.chainName} : ${it.throwable.message} ") - it.throwable.printStackTrace() +// println("!!! NFTCollection.Loaded.WithFailure: ${it.chainName} : ${it.throwable.message} ") +// it.throwable.printStackTrace() // chainsWithFailedRequests.add(it.chainName) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/manageassets/ManageAssetsViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/manageassets/ManageAssetsViewModel.kt index 5405cf6049..0965726d74 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/manageassets/ManageAssetsViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/manageassets/ManageAssetsViewModel.kt @@ -15,6 +15,7 @@ import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.isZero import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.models.ChainId import jp.co.soramitsu.coredb.dao.emptyAccountIdValue import jp.co.soramitsu.feature_wallet_impl.R @@ -128,7 +129,7 @@ class ManageAssetsViewModel @Inject constructor( filteredChainAssets.map { chainAsset -> val asset = assets.find { chainAsset.id == it.asset.token.configuration.id && chainAsset.chainId == it.asset.token.configuration.chainId } chainAsset to asset - }.sortedWith(compareBy> { + }.sortedWith(compareBy> { it.second == null }.thenBy { it.second?.asset?.enabled == false diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt index 3fdf02bad6..dc4beaa127 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt @@ -280,7 +280,7 @@ class ChainRegistry @Inject constructor( override fun getConnection(chainId: String) = connectionPool.getConnectionOrThrow(chainId) - override suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) + suspend fun awaitConnection(chainId: ChainId) = connectionPool.awaitConnection(chainId) @Deprecated( "Since we have ethereum chains, which don't have runtime, we must use the function with nullable return value", ReplaceWith("getRuntimeOrNull(chainId)") From 4f0cd0739ba472574d0d902fcc305749b7d6509b Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 22 Jul 2024 19:04:16 +0500 Subject: [PATCH 31/84] refactor wip --- .../app/root/navigation/Navigator.kt | 5 +- .../main/res/navigation/main_nav_graph.xml | 11 +- .../navigation/InternalPoolsRouter.kt | 3 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 4 +- .../impl/domain/PoolsInteractorImpl.kt | 2 +- .../navigation/InternalPoolsRouterImpl.kt | 5 +- .../allpools/AllPoolsPresenter.kt | 103 ++++++++ .../presentation/allpools/AllPoolsScreen.kt | 60 ----- .../allpools/AllPoolsViewModel.kt | 221 ------------------ ...lPoolsFragment.kt => PoolsFlowFragment.kt} | 140 +++++++---- .../allpools/PoolsFlowViewModel.kt | 203 ++++++++++++++++ .../liquidityadd/LiquidityAddPresenter.kt | 4 - .../liquidityadd/LiquidityAddScreen.kt | 54 ----- .../LiquidityAddConfirmPresenter.kt | 6 +- .../LiquidityAddConfirmScreen.kt | 35 --- .../pooldetails/PoolDetailsPresenter.kt | 109 +++++++++ .../pooldetails/PoolDetailsScreen.kt | 37 +-- .../poollist/PoolListPresenter.kt | 91 ++++++++ .../presentation/poollist/PoolListScreen.kt | 41 ---- .../usecase/ValidateAddLiquidityUseCase.kt | 8 +- .../polkaswap/api/data/PolkaswapRepository.kt | 2 +- .../impl/data/PolkaswapRepositoryImpl.kt | 10 +- 22 files changed, 628 insertions(+), 526 deletions(-) create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt delete mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt rename feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/{AllPoolsFragment.kt => PoolsFlowFragment.kt} (59%) create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowViewModel.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 02077d6986..0f8ed08365 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -73,6 +73,7 @@ import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.CustomContr import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.model.CustomContributePayload import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.CrowdloanContributeFragment import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.parcel.ContributePayload +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.PoolsFlowFragment import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.nft.impl.presentation.NFTFlowFragment import jp.co.soramitsu.nft.navigation.NFTRouter @@ -178,7 +179,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -1521,6 +1521,7 @@ class Navigator : } override fun openPools(chainId: ChainId) { - navController?.navigate(R.id.allPoolsFragment) + val bundle = PoolsFlowFragment.getBundle(chainId) + navController?.navigate(R.id.poolsFlowFragment, bundle) } } diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 75973851ee..73606b3cb6 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -935,14 +935,9 @@ - - + android:id="@+id/poolsFlowFragment" + android:name="jp.co.soramitsu.liquiditypools.impl.presentation.allpools.PoolsFlowFragment" + android:label="poolsFlowFragment" /> fun back() - fun openAllPoolsScreen() + fun openAllPoolsScreen(chainId: ChainId) fun openDetailsPoolScreen(ids: StringPair) fun openAddLiquidityScreen(ids: StringPair) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 6b9f8b499f..98088713e8 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -12,7 +12,9 @@ sealed interface LiquidityPoolsNavGraphRoute { override val routeName: String = "Loading" } - class AllPoolsScreen: LiquidityPoolsNavGraphRoute by Companion { + class AllPoolsScreen( + chainId: ChainId + ): LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "AllPoolsScreen" } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 08d0e61deb..21f7fc0a93 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -149,6 +149,6 @@ class PoolsInteractorImpl( // ) // ) } - return status?.first ?: "" + return status?.getOrNull() ?: "" } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 3fb4b93fb2..e06a810a1f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -6,6 +6,7 @@ import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -29,8 +30,8 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableActionsFlow.tryEmit(NavAction.BackPressed) } - override fun openAllPoolsScreen() { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen()) + override fun openAllPoolsScreen(chainId: ChainId) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen(chainId)) } override fun openDetailsPoolScreen(ids: StringPair) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt new file mode 100644 index 0000000000..cf30663f11 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -0,0 +1,103 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatCryptoDetail +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +class AllPoolsPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, +) : AllPoolsScreenInterface { + + private val _stateSlippage = MutableStateFlow(0.5) + val stateSlippage = _stateSlippage.asStateFlow() + + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + + val pools = flowOf { poolsInteractor.getBasicPools() }.map { pools -> + pools.sortedWith { o1, o2 -> + compareNullDesc(o1.tvl, o2.tvl) + } + }.map { + it.map(BasicPoolData::toListItemState) + } + + init { + + } + private val stateFlow = MutableStateFlow(AllPoolsState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + pools.onEach { + stateFlow.value = stateFlow.value.copy(pools = it) + }.launchIn(coroutineScope) + } + + + override fun onPoolClicked(pair: StringPair) { + val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + internalPoolsRouter.openDetailsPoolScreen(xorPswap) + } + + override fun onMoreClick() { + internalPoolsRouter.openPoolListScreen() + } + + private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = + this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || + this.token.configuration.id.lowercase().contains(filter.lowercase()) + + +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 5d241536fa..ebd59fac3a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -11,48 +11,28 @@ 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.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import jp.co.soramitsu.androidfoundation.format.StringPair -import jp.co.soramitsu.common.R.drawable import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.BottomSheetScreen import jp.co.soramitsu.common.compose.component.Image -import jp.co.soramitsu.common.compose.component.NavigationIconButton import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_liquiditypools_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen -import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen -import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen -import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.ui_core.resources.Dimens -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach data class AllPoolsState( val pools: List = listOf() @@ -60,8 +40,6 @@ data class AllPoolsState( interface AllPoolsScreenInterface { fun onPoolClicked(pair: StringPair) - fun onNavigationClick() - fun onCloseClick() fun onMoreClick() } @@ -74,8 +52,6 @@ fun AllPoolsScreen( Column( modifier = Modifier.fillMaxSize(), ) { - Toolbar(callback) - val listState = rememberLazyListState() BackgroundCorneredWithBorder( @@ -158,40 +134,6 @@ private fun PoolGroupHeader(callback: AllPoolsScreenInterface) { } } -@Composable -private fun Toolbar(callback: AllPoolsScreenInterface) { - Row( - modifier = Modifier - .wrapContentHeight() - .padding(bottom = 12.dp) - ) { - NavigationIconButton( - modifier = Modifier.padding(start = 16.dp), - onNavigationClick = callback::onNavigationClick - ) - - Image( - modifier = Modifier - .padding(start = 8.dp) - .align(Alignment.Top) - .size( - width = 100.dp, - height = 28.dp - ), - res = R.drawable.logo_polkaswap_big, - contentDescription = null - ) - Spacer(modifier = Modifier.weight(1f)) - NavigationIconButton( - modifier = Modifier - .align(Alignment.Top) - .padding(end = 16.dp), - navigationIconResId = drawable.ic_cross_32, - onNavigationClick = callback::onCloseClick - ) - } -} - @Preview @Composable private fun PreviewAllPoolsInternal() { @@ -218,8 +160,6 @@ val itemState = BasicPoolListItemState( ), callback = object : AllPoolsScreenInterface { override fun onPoolClicked(pair: StringPair) {} - override fun onNavigationClick() {} - override fun onCloseClick() {} override fun onMoreClick() {} }, ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt deleted file mode 100644 index b1f9742614..0000000000 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsViewModel.kt +++ /dev/null @@ -1,221 +0,0 @@ -package jp.co.soramitsu.liquiditypools.impl.presentation.allpools - -import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import javax.inject.Inject -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor -import jp.co.soramitsu.account.api.domain.model.address -import jp.co.soramitsu.androidfoundation.format.StringPair -import jp.co.soramitsu.androidfoundation.format.compareNullDesc -import jp.co.soramitsu.common.base.BaseViewModel -import jp.co.soramitsu.common.utils.flowOf -import jp.co.soramitsu.common.utils.formatCrypto -import jp.co.soramitsu.common.utils.formatFiat -import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor -import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddCallbacks -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddPresenter -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddState -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmCallbacks -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmPresenter -import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmState -import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsCallbacks -import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsState -import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreenInterface -import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListState -import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter -import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter -import jp.co.soramitsu.liquiditypools.navigation.NavAction -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId -import jp.co.soramitsu.shared_utils.extensions.fromHex -import jp.co.soramitsu.wallet.impl.domain.model.Asset -import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.lastOrNull -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch - -@HiltViewModel -class AllPoolsViewModel @Inject constructor( - private val poolsInteractor: PoolsInteractor, - private val coroutinesStore: CoroutinesStore, - private val poolsRouter: LiquidityPoolsRouter, - private val innerPoolsRouter: InternalPoolsRouter, - private val accountInteractor: AccountInteractor, - private val liquidityAddPresenter: LiquidityAddPresenter, - liquidityAddConfirmPresenter: LiquidityAddConfirmPresenter, -) : BaseViewModel(), AllPoolsScreenInterface, PoolListScreenInterface, PoolDetailsCallbacks, - LiquidityAddCallbacks by liquidityAddPresenter, - LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter -{ - - val navGraphRoutesFlow: StateFlow = - innerPoolsRouter.createNavGraphRoutesFlow().stateIn( - scope = CoroutineScope(Dispatchers.Main.immediate), - started = SharingStarted.Eagerly, - initialValue = LiquidityPoolsNavGraphRoute.Loading - ) - val navGraphActionsFlow: SharedFlow = - innerPoolsRouter.createNavGraphActionsFlow().shareIn( - scope = CoroutineScope(Dispatchers.Main.immediate), - started = SharingStarted.Eagerly, - replay = 1 - ) - val liquidityAddScreenState: StateFlow = - liquidityAddPresenter.createScreenStateFlow(coroutinesStore.uiScope) - - val liquidityAddConfirmState: StateFlow = - liquidityAddConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) - - private val enteredAssetQueryFlow = MutableStateFlow("") - - private val _state = MutableStateFlow(AllPoolsState()) - private val _poolListState = MutableStateFlow(PoolListState()) - private val _poolDetailState = MutableStateFlow(PoolDetailsState()) - val pools = combine( - flowOf { poolsInteractor.getBasicPools() }, - enteredAssetQueryFlow - ) { pools, query -> - pools.filter { - it.baseToken.isMatchFilter(query) - }.sortedWith { o1, o2 -> - compareNullDesc(o1.tvl, o2.tvl) - } - }.map { - it.map(BasicPoolData::toListItemState) - }.share() - - val state = _state.asStateFlow() - val poolListState = _poolListState.asStateFlow() - val poolDetailState = _poolDetailState.asStateFlow() - - private val poolDetailsScreenArgsFlow = innerPoolsRouter.createNavGraphRoutesFlow() - .filterIsInstance() - .shareIn(this, SharingStarted.Eagerly, 1) - - init { - subscribeScreenState() - launch { - poolsInteractor.updateApy() - } - innerPoolsRouter.openAllPoolsScreen() - - liquidityAddConfirmState.onEach { - println("!!! flow liquidityAddConfirmState: $it") - } - } - - private fun Asset.isMatchFilter(filter: String): Boolean = - this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || - this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - this.token.configuration.id.lowercase().contains(filter.lowercase()) - - private fun subscribeScreenState() { - pools.onEach { - _state.value = _state.value.copy(pools = it) - _poolListState.value = _poolListState.value.copy(pools = it) - }.launchIn(this) - - poolDetailsScreenArgsFlow.onEach { - requestPoolDetails(it.ids)?.let { - _poolDetailState.value = it - } - }.launchIn(this) - } - - suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { - val soraChain = accountInteractor.getChain(soraMainChainId) - val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() - val baseAsset = soraChain.assets.firstOrNull { it.id == ids.first } - val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } - val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") - val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") - - val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { - PoolDetailsState( - originTokenIcon = GradientIconData(baseAsset.iconUrl, null), - destinationTokenIcon = GradientIconData(targetAsset.iconUrl, null), - fromTokenSymbol = baseAsset.symbol, - toTokenSymbol = targetAsset.symbol, - tvl = null, - apy = null - ) - } - return retur - } - - override fun onPoolClicked(pair: StringPair) { - val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") - innerPoolsRouter.openDetailsPoolScreen(xorPswap) - } - - override fun onNavigationClick() { - innerPoolsRouter.back() - } - - override fun onCloseClick() { - innerPoolsRouter.back() - } - - override fun onMoreClick() { - innerPoolsRouter.openPoolListScreen() - } - - override fun onAssetSearchEntered(value: String) { - enteredAssetQueryFlow.value = value - } - - fun onDestinationChanged(route: String) { - println("!!! onDestinationChanged: $route") - } - - fun exitFlow() { - poolsRouter.back() - } - - override fun onSupplyLiquidityClick() { - launch { - poolDetailsScreenArgsFlow.map { - it.ids - }.firstOrNull()?.let { ids -> - innerPoolsRouter.openAddLiquidityScreen(ids) - } - } - } - - override fun onRemoveLiquidityClick() { - println("!!! onRemoveLiquidityClick") - } -} - -fun BasicPoolData.toListItemState(): BasicPoolListItemState { - val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) - ?.multiply(this.baseReserves) - - return BasicPoolListItemState( - ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.token?.configuration?.id.orEmpty()), // todo - token1Icon = this.baseToken.token.configuration.iconUrl, - token2Icon = this.targetToken?.token?.configuration?.iconUrl.orEmpty(), - text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}", - text2 = tvl?.formatFiat().orEmpty(), - text3 = this.sbapy?.let { - "%s%%".format(it.toBigDecimal().formatCrypto()) - }.orEmpty(), - ) -} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowFragment.kt similarity index 59% rename from feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt rename to feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowFragment.kt index b604bb818c..0714124e63 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowFragment.kt @@ -10,16 +10,18 @@ import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.dp +import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -32,20 +34,38 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment +import jp.co.soramitsu.common.compose.component.BottomSheetDialog import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.MainToolbarShimmer +import jp.co.soramitsu.common.compose.component.ToolbarBottomSheet +import jp.co.soramitsu.common.compose.component.ToolbarHomeIconState +import jp.co.soramitsu.common.compose.models.TextModel +import jp.co.soramitsu.common.compose.models.retrieveString +import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @AndroidEntryPoint -class AllPoolsFragment : BaseComposeBottomSheetDialogFragment() { +class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment() { - override val viewModel: AllPoolsViewModel by viewModels() + companion object { + const val POLKASWAP_CHAIN_ID = "polkaswapChainId" + + fun getBundle( + polkaswapChainId: ChainId, + ) = bundleOf( + POLKASWAP_CHAIN_ID to polkaswapChainId + ) + } + + override val viewModel: PoolsFlowViewModel by viewModels() // Compose BackHandler does not work in DialogFragments, nor does BackPressedDispatcher override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @@ -77,6 +97,7 @@ class AllPoolsFragment : BaseComposeBottomSheetDialogFragment @Composable override fun Content(padding: PaddingValues) { val navController = rememberNavController() + val toolbarState = viewModel.toolbarStateFlow.collectAsStateWithLifecycle() SetupNavDestinationChangedListener( navController = navController, @@ -102,6 +123,7 @@ class AllPoolsFragment : BaseComposeBottomSheetDialogFragment viewModel.exitFlow() } } + is NavAction.ShowError -> showErrorDialog( title = it.errorTitle ?: resources.getString(jp.co.soramitsu.common.R.string.common_error_general_title), @@ -111,65 +133,87 @@ class AllPoolsFragment : BaseComposeBottomSheetDialogFragment }.launchIn(this) } - NavHost( - startDestination = LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName, - contentAlignment = Alignment.TopCenter, - navController = navController, - modifier = Modifier - .fillMaxSize(), + BottomSheetDialog( + modifier = Modifier.fillMaxSize() ) { + when (val loadingState = toolbarState.value) { + is LoadingState.Loaded -> + ToolbarBottomSheet( + modifier = Modifier.padding(horizontal = 16.dp), + title = loadingState.data.retrieveString(), + onNavigationClick = remember { + { + viewModel.onNavigationClick() + } + } + ) - composable(LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName) { - val allPoolsScreenState by viewModel.state.collectAsState() - BottomSheetScreen { - AllPoolsScreen( - state = allPoolsScreenState, - callback = viewModel + is LoadingState.Loading -> + MainToolbarShimmer( + homeIconState = ToolbarHomeIconState() ) - } } - composable(LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName) { - val poolListState by viewModel.poolListState.collectAsState() - BottomSheetScreen { - PoolListScreen( - state = poolListState, - callback = viewModel - ) + NavHost( + startDestination = LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName, + contentAlignment = Alignment.TopCenter, + navController = navController, + modifier = Modifier + .fillMaxSize(), + ) { + + composable(LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName) { + val allPoolsScreenState by viewModel.allPoolsScreenState.collectAsStateWithLifecycle() + BottomSheetScreen { + AllPoolsScreen( + state = allPoolsScreenState, + callback = viewModel + ) + } } - } - composable(LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName) { - val poolDetailState by viewModel.poolDetailState.collectAsState() - BottomSheetScreen { - PoolDetailsScreen( - state = poolDetailState, - callbacks = viewModel - ) + composable(LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName) { + val poolListState by viewModel.poolListScreenState.collectAsStateWithLifecycle() + BottomSheetScreen { + PoolListScreen( + state = poolListState, + callback = viewModel + ) + } } - } - composable(LiquidityPoolsNavGraphRoute.LiquidityAddScreen.routeName) { - val liquidityAddState by viewModel.liquidityAddScreenState.collectAsState() - BottomSheetScreen { - LiquidityAddScreen(liquidityAddState, viewModel) + composable(LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName) { + val poolDetailState by viewModel.poolDetailsScreenState.collectAsStateWithLifecycle() + BottomSheetScreen { + PoolDetailsScreen( + state = poolDetailState, + callbacks = viewModel + ) + } + } + + composable(LiquidityPoolsNavGraphRoute.LiquidityAddScreen.routeName) { + val liquidityAddState by viewModel.liquidityAddScreenState.collectAsStateWithLifecycle() + BottomSheetScreen { + LiquidityAddScreen(liquidityAddState, viewModel) + } } - } - composable(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen.routeName) { - val liquidityAddConfirmState by viewModel.liquidityAddConfirmState.collectAsStateWithLifecycle() - BottomSheetScreen { - LiquidityAddConfirmScreen(liquidityAddConfirmState, viewModel) + composable(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen.routeName) { + val liquidityAddConfirmState by viewModel.liquidityAddConfirmState.collectAsStateWithLifecycle() + BottomSheetScreen { + LiquidityAddConfirmScreen(liquidityAddConfirmState, viewModel) + } } - } - composable(LiquidityPoolsNavGraphRoute.Loading.routeName) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { CircularProgressIndicator() } - } + composable(LiquidityPoolsNavGraphRoute.Loading.routeName) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { CircularProgressIndicator() } + } + } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowViewModel.kt new file mode 100644 index 0000000000..a5b1ef5814 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/PoolsFlowViewModel.kt @@ -0,0 +1,203 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.allpools + +import androidx.lifecycle.SavedStateHandle +import dagger.hilt.android.lifecycle.HiltViewModel +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.base.BaseViewModel +import jp.co.soramitsu.common.compose.models.TextModel +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddState +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmState +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsState +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListPresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreenInterface +import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListState +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.NavAction +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@HiltViewModel +class PoolsFlowViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + allPoolsPresenter: AllPoolsPresenter, + poolListPresenter: PoolListPresenter, + poolDetailsPresenter: PoolDetailsPresenter, + liquidityAddPresenter: LiquidityAddPresenter, + liquidityAddConfirmPresenter: LiquidityAddConfirmPresenter, + private val coroutinesStore: CoroutinesStore, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, + private val internalPoolsRouter: InternalPoolsRouter, + private val poolsRouter: LiquidityPoolsRouter, +) : BaseViewModel(), + LiquidityAddCallbacks by liquidityAddPresenter, + LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter, + AllPoolsScreenInterface by allPoolsPresenter, + PoolListScreenInterface by poolListPresenter, + PoolDetailsCallbacks by poolDetailsPresenter +{ + + val allPoolsScreenState: StateFlow = + allPoolsPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + val poolListScreenState: StateFlow = + poolListPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + val poolDetailsScreenState: StateFlow = + poolDetailsPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + val liquidityAddScreenState: StateFlow = + liquidityAddPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + val liquidityAddConfirmState: StateFlow = + liquidityAddConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) + + + val navGraphRoutesFlow: StateFlow = + internalPoolsRouter.createNavGraphRoutesFlow().stateIn( + scope = coroutinesStore.uiScope, + started = SharingStarted.Eagerly, + initialValue = LiquidityPoolsNavGraphRoute.Loading + ) + val navGraphActionsFlow: SharedFlow = + internalPoolsRouter.createNavGraphActionsFlow().shareIn( + scope = coroutinesStore.uiScope, + started = SharingStarted.Eagerly, + replay = 1 + ) + + private val polkaswapChainId = savedStateHandle.get(PoolsFlowFragment.POLKASWAP_CHAIN_ID) + ?: error("Can't find ${PoolsFlowFragment.POLKASWAP_CHAIN_ID} in arguments") + + init { + internalPoolsRouter.openAllPoolsScreen(polkaswapChainId) + + subscribeScreenState() + launch { + poolsInteractor.updateApy() + } + } + + private val mutableToolbarStateFlow = MutableStateFlow>(LoadingState.Loading()) + val toolbarStateFlow: StateFlow> = mutableToolbarStateFlow + + fun onDestinationChanged(route: String) { + val newToolbarState: LoadingState = when (route) { + LiquidityPoolsNavGraphRoute.Loading.routeName -> + LoadingState.Loading() + + LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("All pools") + ) + + LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName -> { +// val destinationArgs = innternalPoolsRouter.destination(LiquidityPoolsNavGraphRoute.ListPoolsScreen::class.java) +// val title = destinationArgs?.token?.title.orEmpty() + + LoadingState.Loaded( + TextModel.SimpleString("Your pools") + ) + } + + LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("Pools details") + ) + + LiquidityPoolsNavGraphRoute.LiquidityAddScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("Supply liquidity") + ) + + else -> LoadingState.Loading() + } + + mutableToolbarStateFlow.value = newToolbarState + } + + override fun onPoolClicked(pair: StringPair) { + val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + internalPoolsRouter.openDetailsPoolScreen(xorPswap) + } + + fun onNavigationClick() { + internalPoolsRouter.back() + } + + + private fun subscribeScreenState() { +// poolDetailsScreenArgsFlow.onEach { +// requestPoolDetails(it.ids)?.let { +// _poolDetailState.value = it +// } +// }.launchIn(this) + } +// +// suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { +// val soraChain = accountInteractor.getChain(soraMainChainId) +// val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() +// val baseAsset = soraChain.assets.firstOrNull { it.id == ids.first } +// val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } +// val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") +// val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") +// +// val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { +// PoolDetailsState( +// originTokenIcon = GradientIconData(baseAsset.iconUrl, null), +// destinationTokenIcon = GradientIconData(targetAsset.iconUrl, null), +// fromTokenSymbol = baseAsset.symbol, +// toTokenSymbol = targetAsset.symbol, +// tvl = null, +// apy = null +// ) +// } +// return retur +// } + + fun exitFlow() { + poolsRouter.back() + } +// +// override fun onRemoveLiquidityClick() { +// println("!!! onRemoveLiquidityClick") +// } +} + +fun BasicPoolData.toListItemState(): BasicPoolListItemState { + val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) + ?.multiply(this.baseReserves) + + return BasicPoolListItemState( + ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.token?.configuration?.id.orEmpty()), // todo + token1Icon = this.baseToken.token.configuration.iconUrl, + token2Icon = this.targetToken?.token?.configuration?.iconUrl.orEmpty(), + text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}", + text2 = tvl?.formatFiat().orEmpty(), + text3 = this.sbapy?.let { + "%s%%".format(it.toBigDecimal().formatCrypto()) + }.orEmpty(), + ) +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 07a691e18e..c2893207ad 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -388,10 +388,6 @@ class LiquidityAddPresenter @Inject constructor( return result ?: BigDecimal.ZERO } - override fun onNavigationClick() { - internalPoolsRouter.back() - } - private fun setButtonLoading(loading: Boolean) { stateFlow.value = stateFlow.value.copy( buttonLoading = loading diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index 7abdb6220e..5655e47a8e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -2,32 +2,22 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import java.math.BigDecimal @@ -36,23 +26,14 @@ import jp.co.soramitsu.common.compose.component.AmountInput import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.BottomSheetScreen -import jp.co.soramitsu.common.compose.component.DoubleGradientIcon import jp.co.soramitsu.common.compose.component.FeeInfoViewState -import jp.co.soramitsu.common.compose.component.GradientIconState -import jp.co.soramitsu.common.compose.component.H4Bold -import jp.co.soramitsu.common.compose.component.InfoTable import jp.co.soramitsu.common.compose.component.InfoTableItem import jp.co.soramitsu.common.compose.component.InfoTableItemAsset -import jp.co.soramitsu.common.compose.component.MarginHorizontal import jp.co.soramitsu.common.compose.component.MarginVertical -import jp.co.soramitsu.common.compose.component.NavigationIconButton import jp.co.soramitsu.common.compose.component.TitleIconValueState import jp.co.soramitsu.common.compose.component.TitleValueViewState -import jp.co.soramitsu.common.compose.theme.alertYellow import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.colorAccentDark -import jp.co.soramitsu.common.compose.theme.customColors -import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 @@ -70,8 +51,6 @@ data class LiquidityAddState( interface LiquidityAddCallbacks { - fun onNavigationClick() - fun onReviewClick() fun onFromAmountChange(amount: BigDecimal) @@ -100,8 +79,6 @@ fun LiquidityAddScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - Toolbar(callbacks) - Column( modifier = Modifier .weight(1f) @@ -199,36 +176,6 @@ fun LiquidityAddScreen( } } -@Composable -private fun Toolbar(callback: LiquidityAddCallbacks) { - Row( - modifier = Modifier - .wrapContentHeight() - .padding(bottom = 12.dp) - ) { - NavigationIconButton( - modifier = Modifier.padding(start = 16.dp), - onNavigationClick = callback::onNavigationClick - ) - - H4Bold( - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically), - text = "Supply liquidity", - textAlign = TextAlign.Center - ) - - NavigationIconButton( - modifier = Modifier - .align(Alignment.Top) - .padding(end = 16.dp), - navigationIconResId = R.drawable.ic_cross_32, - onNavigationClick = callback::onNavigationClick - ) - } -} - @Preview @Composable private fun PreviewLiquidityAddScreen() { @@ -242,7 +189,6 @@ private fun PreviewLiquidityAddScreen() { slippage = "0.5%" ), callbacks = object : LiquidityAddCallbacks { - override fun onNavigationClick() {} override fun onReviewClick() {} override fun onFromAmountChange(amount: BigDecimal) {} override fun onFromAmountFocusChange(isFocused: Boolean) {} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 9af82de551..3babd7e209 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -198,13 +198,9 @@ class LiquidityAddConfirmPresenter @Inject constructor( return result ?: BigDecimal.ZERO } - override fun onNavigationClick() { - internalPoolsRouter.back() - } - override fun onConfirmClick() { println("!!! LiquidityAddConfirm onConfirmClick") - coroutinesStore.uiScope.launch { + coroutinesStore.ioScope.launch { val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch val tokento = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch val amountFrom = screenArgsFlow.firstOrNull()?.amountFrom.orZero() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 710039d328..b6fcb4eb30 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -64,8 +64,6 @@ data class LiquidityAddConfirmState( interface LiquidityAddConfirmCallbacks { - fun onNavigationClick() - fun onConfirmClick() } @@ -80,8 +78,6 @@ fun LiquidityAddConfirmScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - Toolbar(callbacks) - Column( modifier = Modifier .weight(1f) @@ -218,36 +214,6 @@ fun LiquidityAddConfirmScreen( } } -@Composable -private fun Toolbar(callback: LiquidityAddConfirmCallbacks) { - Row( - modifier = Modifier - .wrapContentHeight() - .padding(bottom = 12.dp) - ) { - NavigationIconButton( - modifier = Modifier.padding(start = 16.dp), - onNavigationClick = callback::onNavigationClick - ) - - H4Bold( - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically), - text = "Confirm liquidity", - textAlign = TextAlign.Center - ) - - NavigationIconButton( - modifier = Modifier - .align(Alignment.Top) - .padding(end = 16.dp), - navigationIconResId = R.drawable.ic_cross_32, - onNavigationClick = callback::onNavigationClick - ) - } -} - @Preview @Composable private fun PreviewLiquidityAddConfirmScreen() { @@ -259,7 +225,6 @@ private fun PreviewLiquidityAddConfirmScreen() { feeInfo = FeeInfoViewState.default, ), callbacks = object : LiquidityAddConfirmCallbacks { - override fun onNavigationClick() {} override fun onConfirmClick() {} }, ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt new file mode 100644 index 0000000000..a4191df08a --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -0,0 +1,109 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails + +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.toListItemState +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +class PoolDetailsPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, +) : PoolDetailsCallbacks { + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + + init { + + } + + private val stateFlow = MutableStateFlow(PoolDetailsState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + screenArgsFlow.onEach { + requestPoolDetails(it.ids)?.let { + stateFlow.value = it + } + }.launchIn(coroutineScope) + } + + + override fun onSupplyLiquidityClick() { + coroutinesStore.ioScope.launch { + screenArgsFlow.map { + it.ids + }.firstOrNull()?.let { ids -> + internalPoolsRouter.openAddLiquidityScreen(ids) + } + } + } + + override fun onRemoveLiquidityClick() { + println("!!! onRemoveLiquidityClick") + } + + suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { + val soraChain = accountInteractor.getChain(soraMainChainId) + val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() + val baseAsset = soraChain.assets.firstOrNull { it.id == ids.first } + val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } + val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") + val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") + + val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { + PoolDetailsState( + originTokenIcon = GradientIconData(baseAsset.iconUrl, null), + destinationTokenIcon = GradientIconData(targetAsset.iconUrl, null), + fromTokenSymbol = baseAsset.symbol, + toTokenSymbol = targetAsset.symbol, + tvl = null, + apy = null + ) + } + return retur + } + + private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = + this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || + this.token.configuration.id.lowercase().contains(filter.lowercase()) + + +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 07b6b6e7cc..0a8a028790 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -48,8 +48,6 @@ data class PoolDetailsState( interface PoolDetailsCallbacks { - fun onNavigationClick() - fun onSupplyLiquidityClick() fun onRemoveLiquidityClick() } @@ -64,8 +62,6 @@ fun PoolDetailsScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - Toolbar(callbacks) - Column( modifier = Modifier .weight(1f) @@ -159,36 +155,6 @@ fun PoolDetailsScreen( } } -@Composable -private fun Toolbar(callback: PoolDetailsCallbacks) { - Row( - modifier = Modifier - .wrapContentHeight() - .padding(bottom = 12.dp) - ) { - NavigationIconButton( - modifier = Modifier.padding(start = 16.dp), - onNavigationClick = callback::onNavigationClick - ) - - H4Bold( - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically), - text = "Pools details", - textAlign = TextAlign.Center - ) - - NavigationIconButton( - modifier = Modifier - .align(Alignment.Top) - .padding(end = 16.dp), - navigationIconResId = jp.co.soramitsu.common.R.drawable.ic_cross_32, - onNavigationClick = callback::onNavigationClick - ) - } -} - private fun provideGradientIconState(gradientIconData: GradientIconData?): GradientIconState { val url = gradientIconData?.url return if (url == null) { @@ -204,7 +170,7 @@ private fun provideGradientIconState(gradientIconData: GradientIconData?): Gradi } -@Preview() +@Preview @Composable private fun PreviewPoolDetailsScreen() { BottomSheetScreen { @@ -218,7 +184,6 @@ private fun PreviewPoolDetailsScreen() { tvl = "$34.999 TVL", ), callbacks = object : PoolDetailsCallbacks { - override fun onNavigationClick() {} override fun onSupplyLiquidityClick() {} override fun onRemoveLiquidityClick() {} }, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt new file mode 100644 index 0000000000..42681b3da2 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -0,0 +1,91 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.poollist + +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.utils.flowOf +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.toListItemState +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn + +class PoolListPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, +) : PoolListScreenInterface { + + private val enteredAssetQueryFlow = MutableStateFlow("") + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) + + + val pools = combine( + flowOf { poolsInteractor.getBasicPools() }, + enteredAssetQueryFlow + ) { pools, query -> + pools.filter { + it.isFilterMatch(query) + }.sortedWith { o1, o2 -> + compareNullDesc(o1.tvl, o2.tvl) + } + }.map { + it.map(BasicPoolData::toListItemState) + } + + init { + + } + + private val stateFlow = MutableStateFlow(PoolListState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + pools.onEach { + stateFlow.value = stateFlow.value.copy(pools = it) + }.launchIn(coroutineScope) + } + + + override fun onPoolClicked(pair: StringPair) { + val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + internalPoolsRouter.openDetailsPoolScreen(xorPswap) + } + + + override fun onAssetSearchEntered(value: String) { + enteredAssetQueryFlow.value = value + } + + private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = + this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || + this.token.configuration.id.lowercase().contains(filter.lowercase()) + + +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 6efbbbcb72..1b79a9608c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -1,9 +1,7 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.poollist -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight @@ -14,14 +12,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.BottomSheetScreen import jp.co.soramitsu.common.compose.component.CorneredInput -import jp.co.soramitsu.common.compose.component.H4Bold -import jp.co.soramitsu.common.compose.component.NavigationIconButton import jp.co.soramitsu.common.compose.theme.white04 import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItem @@ -35,8 +30,6 @@ data class PoolListState( interface PoolListScreenInterface { fun onPoolClicked(pair: StringPair) - fun onNavigationClick() - fun onCloseClick() fun onAssetSearchEntered(value: String) } @@ -49,8 +42,6 @@ fun PoolListScreen( Column( modifier = Modifier.fillMaxSize(), ) { - Toolbar(callback) - Box( contentAlignment = Alignment.CenterStart, modifier = Modifier.padding(horizontal = 16.dp) @@ -81,36 +72,6 @@ fun PoolListScreen( } } -@Composable -private fun Toolbar(callback: PoolListScreenInterface) { - Row( - modifier = Modifier - .wrapContentHeight() - .padding(bottom = 12.dp) - ) { - NavigationIconButton( - modifier = Modifier.padding(start = 16.dp), - onNavigationClick = callback::onNavigationClick - ) - - H4Bold( - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically), - text = "Your pools", - textAlign = TextAlign.Center - ) - - NavigationIconButton( - modifier = Modifier - .align(Alignment.Top) - .padding(end = 16.dp), - navigationIconResId = jp.co.soramitsu.common.R.drawable.ic_cross_32, - onNavigationClick = callback::onCloseClick - ) - } -} - @Preview @Composable private fun PreviewPoolListScreen() { @@ -137,8 +98,6 @@ private fun PreviewPoolListScreen() { ), callback = object : PoolListScreenInterface { override fun onPoolClicked(pair: StringPair) {} - override fun onNavigationClick() {} - override fun onCloseClick() {} override fun onAssetSearchEntered(value: String) {} }, ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index c123026c26..093145e382 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -19,13 +19,13 @@ class ValidateAddLiquidityUseCase @Inject constructor() { feeAmount: BigDecimal ): Result { return runCatching { - val isEnoughAmountFrom = amountFrom < assetFrom.asset.total.orZero() + feeAmount.takeIf { + val isEnoughAmountFrom = amountFrom + feeAmount.takeIf { assetFrom.asset.token.configuration.id == utilityAssetId - }.orZero() + }.orZero() < assetFrom.asset.total.orZero() - val isEnoughAmountTo = amountTo < assetTo.asset.total.orZero() + feeAmount.takeIf { + val isEnoughAmountTo = amountTo + feeAmount.takeIf { assetTo.asset.token.configuration.id == utilityAssetId - }.orZero() + }.orZero() < assetTo.asset.total.orZero() val isEnoughAmountFee = if (utilityAssetId in listOf(assetFrom.asset.token.configuration.id, assetTo.asset.token.configuration.id)) { true diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index 7dcdcd5cf7..405604a8d2 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -99,6 +99,6 @@ interface PolkaswapRepository { pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double - ): Pair? + ): Result? } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 3853e3a61e..b8977f241f 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -871,17 +871,23 @@ class PolkaswapRepositoryImpl @Inject constructor( pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double - ): Pair? { + ): Result? { +// ): Pair? { val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountFrom, slippageTolerance) val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTo, slippageTolerance) val dexId = getPoolBaseTokenDexId(tokenFrom.currencyId) val soraChain = accountRepository.getChain(soraMainChainId) + val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId val baseTokenId = tokenFrom.currencyId val targetTokenId = tokenTo.currencyId if (baseTokenId == null || targetTokenId == null) return null - return extrinsicService.submitAndWatchExtrinsic(soraChain) { + return extrinsicService.submitExtrinsic( + chain = soraChain, + accountId = accountId, + useBatchAll = !pairPresented + ) { if (!pairPresented) { if (!pairEnabled) { register( From f3e8f5a7847fa6069e5cf1e50daa190bc051cd1b Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 22 Jul 2024 19:34:57 +0500 Subject: [PATCH 32/84] refactor wip --- .../app/root/navigation/Navigator.kt | 2 +- .../main/res/navigation/main_nav_graph.xml | 2 +- .../{allpools => }/PoolsFlowFragment.kt | 41 +++++++----------- .../{allpools => }/PoolsFlowViewModel.kt | 42 +++---------------- .../allpools/AllPoolsPresenter.kt | 20 +-------- .../presentation/allpools/AllPoolsScreen.kt | 26 ++++++------ .../liquidityadd/LiquidityAddScreen.kt | 35 +++++++--------- .../LiquidityAddConfirmScreen.kt | 27 +++++------- .../pooldetails/PoolDetailsPresenter.kt | 6 --- .../pooldetails/PoolDetailsScreen.kt | 34 +++++++-------- .../poollist/PoolListPresenter.kt | 2 +- .../presentation/poollist/PoolListScreen.kt | 23 ++++------ .../swap_tokens/SwapTokensViewModel.kt | 6 ++- 13 files changed, 91 insertions(+), 175 deletions(-) rename feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/{allpools => }/PoolsFlowFragment.kt (90%) rename feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/{allpools => }/PoolsFlowViewModel.kt (81%) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 0f8ed08365..4520658d2d 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -73,7 +73,7 @@ import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.CustomContr import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.model.CustomContributePayload import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.CrowdloanContributeFragment import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.parcel.ContributePayload -import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.PoolsFlowFragment +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowFragment import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.nft.impl.presentation.NFTFlowFragment import jp.co.soramitsu.nft.navigation.NFTRouter diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 73606b3cb6..c30ac6ac56 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -936,7 +936,7 @@ Date: Tue, 23 Jul 2024 19:54:52 +0500 Subject: [PATCH 33/84] refactor to use predefined chain: soraMainnet or soraTestnet --- common/src/main/res/values/strings.xml | 2 + .../jp/co/soramitsu/coredb/AppDatabase.kt | 9 +- .../jp/co/soramitsu/coredb/dao/PoolDao.kt | 83 +++ .../jp/co/soramitsu/coredb/di/DbModule.kt | 9 +- .../jp/co/soramitsu/coredb/model/PoolLocal.kt | 51 ++ .../soramitsu/coredb/model/PoolLocalJoined.kt | 34 ++ .../domain/interfaces/PoolsInteractor.kt | 27 +- .../navigation/InternalPoolsRouter.kt | 8 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 8 +- .../impl/domain/PoolsInteractorImpl.kt | 74 ++- .../navigation/InternalPoolsRouterImpl.kt | 16 +- .../impl/presentation/PoolsFlowViewModel.kt | 19 +- .../allpools/AllPoolsPresenter.kt | 74 ++- .../presentation/allpools/AllPoolsScreen.kt | 127 ++-- .../allpools/BasicPoolListItem.kt | 14 +- .../liquidityadd/LiquidityAddPresenter.kt | 127 ++-- .../LiquidityAddConfirmPresenter.kt | 98 ++-- .../pooldetails/PoolDetailsPresenter.kt | 66 ++- .../pooldetails/PoolDetailsScreen.kt | 151 +++-- .../poollist/PoolListPresenter.kt | 32 +- .../usecase/ValidateAddLiquidityUseCase.kt | 1 - .../polkaswap/api/data/PolkaswapRepository.kt | 32 +- .../api/domain/PolkaswapInteractor.kt | 2 +- .../api/domain/models/UserPoolData.kt | 2 +- .../impl/data/PolkaswapRepositoryImpl.kt | 550 ++++++++++++------ .../impl/di/PolkaswapFeatureBindModule.kt | 7 +- .../impl/domain/PolkaswapInteractorImpl.kt | 6 +- 27 files changed, 1129 insertions(+), 500 deletions(-) create mode 100644 core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt create mode 100644 core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocal.kt create mode 100644 core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocalJoined.kt diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index e430030e6c..f9b77235ee 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -636,6 +636,8 @@ + %d others Your %s pooled + Your pools + All pools Reset to default Swap Swapped diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt index b062263a5c..dc356981f1 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt @@ -19,6 +19,7 @@ import jp.co.soramitsu.coredb.dao.MetaAccountDao import jp.co.soramitsu.coredb.dao.NomisScoresDao import jp.co.soramitsu.coredb.dao.OperationDao import jp.co.soramitsu.coredb.dao.PhishingDao +import jp.co.soramitsu.coredb.dao.PoolDao import jp.co.soramitsu.coredb.dao.SoraCardDao import jp.co.soramitsu.coredb.dao.StakingTotalRewardDao import jp.co.soramitsu.coredb.dao.StorageDao @@ -81,6 +82,7 @@ import jp.co.soramitsu.coredb.model.AccountLocal import jp.co.soramitsu.coredb.model.AccountStakingLocal import jp.co.soramitsu.coredb.model.AddressBookContact import jp.co.soramitsu.coredb.model.AssetLocal +import jp.co.soramitsu.coredb.model.BasicPoolLocal import jp.co.soramitsu.coredb.model.NomisWalletScoreLocal import jp.co.soramitsu.coredb.model.OperationLocal import jp.co.soramitsu.coredb.model.PhishingLocal @@ -88,6 +90,7 @@ import jp.co.soramitsu.coredb.model.SoraCardInfoLocal import jp.co.soramitsu.coredb.model.StorageEntryLocal import jp.co.soramitsu.coredb.model.TokenPriceLocal import jp.co.soramitsu.coredb.model.TotalRewardLocal +import jp.co.soramitsu.coredb.model.UserPoolLocal import jp.co.soramitsu.coredb.model.chain.ChainAccountLocal import jp.co.soramitsu.coredb.model.chain.ChainAssetLocal import jp.co.soramitsu.coredb.model.chain.ChainExplorerLocal @@ -121,7 +124,9 @@ import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal ChainExplorerLocal::class, SoraCardInfoLocal::class, ChainTypesLocal::class, - NomisWalletScoreLocal::class + NomisWalletScoreLocal::class, + BasicPoolLocal::class, + UserPoolLocal::class ] ) @TypeConverters( @@ -221,4 +226,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun soraCardDao(): SoraCardDao abstract fun nomisScoresDao(): NomisScoresDao + + abstract fun poolDao(): PoolDao } diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt new file mode 100644 index 0000000000..7857e216d1 --- /dev/null +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt @@ -0,0 +1,83 @@ +package jp.co.soramitsu.coredb.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert +import jp.co.soramitsu.coredb.model.BasicPoolLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocalNullable +import jp.co.soramitsu.coredb.model.UserPoolLocal +import kotlinx.coroutines.flow.Flow + +@Dao +interface PoolDao { + + companion object { + private const val userPoolJoinBasic = """ + SELECT * FROM userpools left join allpools on userpools.userTokenIdBase=allpools.tokenIdBase and userpools.userTokenIdTarget=allpools.tokenIdTarget + """ + } + + @Query( + """ + select * from allpools where tokenIdBase=:base and tokenIdTarget=:target + """ + ) + suspend fun getBasicPool(base: String, target: String): BasicPoolLocal? + + @Query("select * from allpools") + suspend fun getBasicPools(): List + + @Query("DELETE FROM userpools where accountAddress = :curAccount") + suspend fun clearTable(curAccount: String) + + @Query("DELETE FROM allpools") + suspend fun clearBasicTable() + + @Query( + """ + $userPoolJoinBasic where userpools.accountAddress = :accountAddress + """ + ) + fun subscribePoolsList(accountAddress: String): Flow> + + @Query( + """ + select * from allpools a left join userpools u on + a.tokenIdBase = u.userTokenIdBase and a.tokenIdTarget = u.userTokenIdTarget + and u.accountAddress is not null + and u.accountAddress = :accountAddress + """ + ) + fun subscribeAllPools(accountAddress: String?): Flow> + + @Query( + """ + $userPoolJoinBasic where userpools.accountAddress = :accountAddress + """ + ) + suspend fun getPoolsList(accountAddress: String): List + + @Query( + """ + select * from allpools a + left join userpools u on a.tokenIdBase = u.userTokenIdBase + and a.tokenIdTarget = u.userTokenIdTarget + and u.accountAddress is not null + and u.accountAddress = :accountAddress + where a.tokenIdBase=:baseTokenId and a.tokenIdTarget=:targetTokenId + """ + ) + fun subscribePool(accountAddress: String, baseTokenId: String, targetTokenId: String): Flow + + @Delete + suspend fun deleteBasicPools(p: List) + + @Upsert() + suspend fun insertBasicPools(pools: List) + + @Upsert() + suspend fun insertUserPools(pools: List) + +} diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt index 4d3e82aae7..1545d12126 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/di/DbModule.kt @@ -5,7 +5,6 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton import jp.co.soramitsu.common.data.secrets.v1.SecretStoreV1 import jp.co.soramitsu.common.data.secrets.v2.SecretStoreV2 import jp.co.soramitsu.coredb.AppDatabase @@ -22,6 +21,8 @@ import jp.co.soramitsu.coredb.dao.SoraCardDao import jp.co.soramitsu.coredb.dao.StakingTotalRewardDao import jp.co.soramitsu.coredb.dao.StorageDao import jp.co.soramitsu.coredb.dao.TokenPriceDao +import javax.inject.Singleton +import jp.co.soramitsu.coredb.dao.PoolDao @InstallIn(SingletonComponent::class) @Module @@ -114,4 +115,10 @@ class DbModule { fun provideNomisScoresDao(appDatabase: AppDatabase): NomisScoresDao { return appDatabase.nomisScoresDao() } + + @Provides + @Singleton + fun providePoolsDao(appDatabase: AppDatabase): PoolDao { + return appDatabase.poolDao() + } } diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocal.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocal.kt new file mode 100644 index 0000000000..793c8ab038 --- /dev/null +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocal.kt @@ -0,0 +1,51 @@ +package jp.co.soramitsu.coredb.model + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import java.math.BigDecimal + +@Entity( + tableName = "allpools", + primaryKeys = ["tokenIdBase", "tokenIdTarget"], +) +data class BasicPoolLocal( + val tokenIdBase: String, + val tokenIdTarget: String, + val reserveBase: BigDecimal, + val reserveTarget: BigDecimal, + val totalIssuance: BigDecimal, + val reservesAccount: String, +) + +@Entity( + tableName = "userpools", + primaryKeys = ["userTokenIdBase", "userTokenIdTarget", "accountAddress"], + indices = [Index(value = ["accountAddress"])], + foreignKeys = [ + ForeignKey( + entity = BasicPoolLocal::class, + parentColumns = ["tokenIdBase", "tokenIdTarget"], + childColumns = ["userTokenIdBase", "userTokenIdTarget"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + ) + ] +) +data class UserPoolLocal( + val userTokenIdBase: String, + val userTokenIdTarget: String, + val accountAddress: String, + val poolProvidersBalance: BigDecimal, +) + +/* +@Entity( + tableName = "poolBaseTokens" +) +data class PoolBaseTokenLocal( + @PrimaryKey val tokenId: String, + val dexId: Int, +) +*/ diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocalJoined.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocalJoined.kt new file mode 100644 index 0000000000..35396a0f96 --- /dev/null +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/PoolLocalJoined.kt @@ -0,0 +1,34 @@ +package jp.co.soramitsu.coredb.model + +import androidx.room.Embedded +import androidx.room.Relation + +data class UserPoolJoinedLocal( + @Embedded + val userPoolLocal: UserPoolLocal, + @Embedded + val basicPoolLocal: BasicPoolLocal, +) + +data class UserPoolJoinedLocalNullable( + @Embedded + val userPoolLocal: UserPoolLocal?, + @Embedded + val basicPoolLocal: BasicPoolLocal, +) + +//data class TokenFiatLocal( +// @Embedded +// val token: TokenLocal, +// @Relation(parentColumn = "id", entityColumn = "tokenIdFiat") +// val fiat: FiatTokenPriceLocal?, +//) +// +//data class BasicPoolWithTokenFiatLocal( +// @Embedded +// val basicPoolLocal: BasicPoolLocal, +// @Relation(parentColumn = "tokenIdBase", entityColumn = "id", entity = TokenLocal::class) +// val tokenBaseLocal: TokenFiatLocal, +// @Relation(parentColumn = "tokenIdTarget", entityColumn = "id", entity = TokenLocal::class) +// val tokenTargetLocal: TokenFiatLocal, +//) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index a4a9abdfc2..7004c858c0 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -1,23 +1,33 @@ package jp.co.soramitsu.liquiditypools.domain.interfaces import java.math.BigDecimal +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData -import jp.co.soramitsu.wallet.impl.domain.model.Asset +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlinx.coroutines.flow.Flow interface PoolsInteractor { - suspend fun getBasicPools(): List + suspend fun getBasicPools(chainId: ChainId): List - suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? + // suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? + fun subscribePoolsCacheOfAccount(address: String): Flow> + suspend fun getPoolData( + chainId: ChainId, + baseTokenId: String, + targetTokenId: String, + ): Flow suspend fun getUserPoolData( + chainId: ChainId, address: String, baseTokenId: String, tokenId: ByteArray ): PoolDataDto? suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, address: String, tokenFrom: jp.co.soramitsu.core.models.Asset, tokenTo: jp.co.soramitsu.core.models.Asset, @@ -29,16 +39,17 @@ interface PoolsInteractor { ): BigDecimal? suspend fun isPairEnabled( + chainId: ChainId, inputTokenId: String, - outputTokenId: String, - accountAddress: String + outputTokenId: String ): Boolean - suspend fun updateApy() +// suspend fun updateApy() fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? suspend fun observeAddLiquidity( + chainId: ChainId, tokenFrom: jp.co.soramitsu.core.models.Asset, tokenTo: jp.co.soramitsu.core.models.Asset, amountFrom: BigDecimal, @@ -47,4 +58,6 @@ interface PoolsInteractor { presented: Boolean, slippageTolerance: Double ): String + + suspend fun updatePools(chainId: ChainId) } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 3941176dcd..b0cf9c8cf4 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -11,12 +11,12 @@ interface InternalPoolsRouter { fun back() fun openAllPoolsScreen(chainId: ChainId) - fun openDetailsPoolScreen(ids: StringPair) + fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) - fun openAddLiquidityScreen(ids: StringPair) - fun openAddLiquidityConfirmScreen(ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) + fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) + fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) - fun openPoolListScreen() + fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) fun openErrorsScreen(title: String? = null, message: String) } \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 98088713e8..57c76a88ee 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -13,7 +13,7 @@ sealed interface LiquidityPoolsNavGraphRoute { } class AllPoolsScreen( - chainId: ChainId + val chainId: ChainId ): LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "AllPoolsScreen" @@ -21,7 +21,8 @@ sealed interface LiquidityPoolsNavGraphRoute { } class ListPoolsScreen( -// val token: NFT + val chainId: ChainId, + val isUserPools: Boolean ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "ListPoolsScreen" @@ -29,6 +30,7 @@ sealed interface LiquidityPoolsNavGraphRoute { } class PoolDetailsScreen( + val chainId: ChainId, val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -37,6 +39,7 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityAddScreen( + val chainId: ChainId, val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -45,6 +48,7 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityAddConfirmScreen( + val chainId: ChainId, val ids: StringPair, val amountFrom: BigDecimal, val amountTo: BigDecimal, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 21f7fc0a93..5dc5fb9a72 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -7,14 +7,16 @@ import jp.co.soramitsu.common.data.secrets.v1.Keypair import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlinx.coroutines.flow.Flow class PoolsInteractorImpl( private val polkaswapRepository: PolkaswapRepository, @@ -24,35 +26,46 @@ class PoolsInteractorImpl( private val keypairProvider: KeypairProvider, ) : PoolsInteractor { - override suspend fun getBasicPools(): List { - return polkaswapRepository.getBasicPools() + override suspend fun getBasicPools(chainId: ChainId): List { + return polkaswapRepository.getBasicPools(chainId) } - override suspend fun getPoolCacheOfCurAccount( - tokenFromId: String, - tokenToId: String - ): CommonUserPoolData? { - val wallet = accountRepository.getSelectedMetaAccount() - val chainId = polkaswapInteractor.polkaswapChainId - val chain = chainRegistry.getChain(chainId) - val address = wallet.address(chain) - return polkaswapRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) + override fun subscribePoolsCacheOfAccount(address: String): Flow> { + return polkaswapRepository.subscribePools(address) } + override suspend fun getPoolData(chainId: ChainId, baseTokenId: String, targetTokenId: String): Flow { + val address = accountRepository.getSelectedAccount(chainId).address + return polkaswapRepository.subscribePool(address, baseTokenId, targetTokenId) + } + +// override suspend fun getPoolCacheOfCurAccount( +// tokenFromId: String, +// tokenToId: String +// ): CommonUserPoolData? { +// val wallet = accountRepository.getSelectedMetaAccount() +// val chainId = polkaswapInteractor.polkaswapChainId +// val chain = chainRegistry.getChain(chainId) +// val address = wallet.address(chain) +// return polkaswapRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) +// } + override suspend fun getUserPoolData( + chainId: ChainId, address: String, baseTokenId: String, tokenId: ByteArray ): PoolDataDto? { // return polkaswapRepository.getPoolOfAccount(address, baseTokenId, tokenId.toHexString(true), polkaswapInteractor.polkaswapChainId) - return polkaswapRepository.getUserPoolData(address, baseTokenId, tokenId) + return polkaswapRepository.getUserPoolData(chainId, address, baseTokenId, tokenId) } override suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, address: String, - tokenFrom: jp.co.soramitsu.core.models.Asset, - tokenTo: jp.co.soramitsu.core.models.Asset, + tokenFrom: Asset, + tokenTo: Asset, tokenFromAmount: BigDecimal, tokenToAmount: BigDecimal, pairEnabled: Boolean, @@ -60,6 +73,7 @@ class PoolsInteractorImpl( slippageTolerance: Double ): BigDecimal? { return polkaswapRepository.calcAddLiquidityNetworkFee( + chainId, address, tokenFrom, tokenTo, @@ -71,17 +85,17 @@ class PoolsInteractorImpl( ) } - override suspend fun updateApy() { - polkaswapInteractor.updatePoolsSbApy() - } +// override suspend fun updateApy() { +// polkaswapInteractor.updatePoolsSbApy() +// } override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? = polkaswapInteractor.getPoolStrategicBonusAPY(reserveAccountOfPool) - override suspend fun isPairEnabled(inputTokenId: String, outputTokenId: String, accountAddress: String): Boolean { - val dexId = polkaswapRepository.getPoolBaseTokenDexId(inputTokenId) + override suspend fun isPairEnabled(chainId: ChainId, inputTokenId: String, outputTokenId: String): Boolean { + val dexId = polkaswapRepository.getPoolBaseTokenDexId(chainId, inputTokenId) return polkaswapRepository.isPairAvailable( - soraMainChainId, + chainId, inputTokenId, outputTokenId, dexId @@ -89,19 +103,21 @@ class PoolsInteractorImpl( } override suspend fun observeAddLiquidity( - tokenFrom: jp.co.soramitsu.core.models.Asset, - tokenTo: jp.co.soramitsu.core.models.Asset, + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, amountFrom: BigDecimal, amountTo: BigDecimal, enabled: Boolean, presented: Boolean, slippageTolerance: Double ): String { - val soraChain = chainRegistry.getChain(soraMainChainId) + val soraChain = chainRegistry.getChain(chainId) val metaAccount = accountRepository.getSelectedMetaAccount() val address = metaAccount.address(soraChain) ?: return "" val networkFee = calcAddLiquidityNetworkFee( + chainId, address, tokenFrom, tokenTo, @@ -120,6 +136,7 @@ class PoolsInteractorImpl( val keypair = Keypair(public, private, nonce) val status = polkaswapRepository.observeAddLiquidity( + chainId, address, keypair, tokenFrom, @@ -151,4 +168,11 @@ class PoolsInteractorImpl( } return status?.getOrNull() ?: "" } + + override suspend fun updatePools(chainId: ChainId) { + val address = accountRepository.getSelectedAccount(chainId).address + polkaswapRepository.updateAccountPools(chainId, address) + + polkaswapRepository.updateBasicPools(chainId) + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index e06a810a1f..65b5ba7edc 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -34,20 +34,20 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen(chainId)) } - override fun openDetailsPoolScreen(ids: StringPair) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(ids)) + override fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(chainId, ids)) } - override fun openAddLiquidityScreen(ids: StringPair) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(ids)) + override fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(chainId, ids)) } - override fun openAddLiquidityConfirmScreen(ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(ids, amountFrom, amountTo, apy)) + override fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(chainId, ids, amountFrom, amountTo, apy)) } - override fun openPoolListScreen() { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen()) + override fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen(chainId, isUserPools)) } override fun openErrorsScreen(title: String?, message: String) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 4cffbbbb1a..23ba0e1ab8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -98,7 +98,8 @@ class PoolsFlowViewModel @Inject constructor( internalPoolsRouter.openAllPoolsScreen(polkaswapChainId) launch { - poolsInteractor.updateApy() +// poolsInteractor.updateApy() + poolsInteractor.updatePools(polkaswapChainId) } } @@ -141,8 +142,8 @@ class PoolsFlowViewModel @Inject constructor( } override fun onPoolClicked(pair: StringPair) { - val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") - internalPoolsRouter.openDetailsPoolScreen(xorPswap) +// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + internalPoolsRouter.openDetailsPoolScreen(polkaswapChainId, pair) } fun onNavigationClick() { @@ -154,18 +155,22 @@ class PoolsFlowViewModel @Inject constructor( } } -fun BasicPoolData.toListItemState(): BasicPoolListItemState { +fun BasicPoolData.toListItemState(): BasicPoolListItemState? { val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) ?.multiply(this.baseReserves) + val baseTokenId = this.baseToken.token.configuration.currencyId ?: return null + val targetTokenId = this.targetToken?.token?.configuration?.currencyId ?: return null + return BasicPoolListItemState( - ids = StringPair(this.baseToken.token.configuration.id, this.targetToken?.token?.configuration?.id.orEmpty()), // todo + ids = StringPair(baseTokenId, targetTokenId), token1Icon = this.baseToken.token.configuration.iconUrl, token2Icon = this.targetToken?.token?.configuration?.iconUrl.orEmpty(), - text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}", - text2 = tvl?.formatFiat().orEmpty(), + text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}".uppercase(), + text2 = tvl?.formatFiat(this.baseToken.token.fiatSymbol).orEmpty(), text3 = this.sbapy?.let { "%s%%".format(it.toBigDecimal().formatCrypto()) }.orEmpty(), + text4 = "Earn PSWAP" ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index e712515e04..050dc4614a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -2,6 +2,7 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.utils.flowOf @@ -18,9 +19,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn @@ -42,14 +46,40 @@ class AllPoolsPresenter @Inject constructor( .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - val pools = flowOf { poolsInteractor.getBasicPools() }.map { pools -> - pools.sortedWith { o1, o2 -> - compareNullDesc(o1.tvl, o2.tvl) - } - }.map { - it.map(BasicPoolData::toListItemState) +// val pools = flowOf { poolsInteractor.getBasicPools() }.map { pools -> +// pools.sortedWith { o1, o2 -> +// compareNullDesc(o1.tvl, o2.tvl) +// } +// }.map { +// it.map(BasicPoolData::toListItemState) +// } + + val chainFlow = screenArgsFlow.map { screenArgs -> + chainsRepository.getChain(screenArgs.chainId) } + val allPools = combine( + accountInteractor.selectedMetaAccountFlow(), + chainFlow + ) { wallet, chain -> + wallet.address(chain) + } + .mapNotNull { it } + .flatMapLatest { address -> + poolsInteractor.subscribePoolsCacheOfAccount(address) + }.map { pools -> + pools.sortedWith { o1, o2 -> + compareNullDesc(o1.basic.tvl, o2.basic.tvl) + }.groupBy { + it.user != null + } + }.map { +// it.map(BasicPoolData::toListItemState) + it.mapValues { it -> + it.value.mapNotNull { it.basic.toListItemState() } + } + } + init { } @@ -61,19 +91,39 @@ class AllPoolsPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { - pools.onEach { - stateFlow.value = stateFlow.value.copy(pools = it) +// pools.onEach { +// stateFlow.value = stateFlow.value.copy(pools = it) +// }.launchIn(coroutineScope) + + allPools.onEach { poolLists -> + val userPools = poolLists[true].orEmpty() + val otherPools = poolLists[false]?.take(10).orEmpty() + + val shownUserPools = userPools.take(10) + val shownOtherPools = otherPools.take(10) + + val hasExtraUserPools = shownUserPools.size < userPools.size + val hasExtraAllPools = shownOtherPools.size < otherPools.size + + stateFlow.value = stateFlow.value.copy( + userPools = userPools, + allPools = otherPools, + hasExtraUserPools = hasExtraUserPools, + hasExtraAllPools = hasExtraAllPools + ) }.launchIn(coroutineScope) } override fun onPoolClicked(pair: StringPair) { - val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") - internalPoolsRouter.openDetailsPoolScreen(xorPswap) +// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return + internalPoolsRouter.openDetailsPoolScreen(chainId, pair) } - override fun onMoreClick() { - internalPoolsRouter.openPoolListScreen() + override fun onMoreClick(isUserPools: Boolean) { + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return + internalPoolsRouter.openPoolListScreen(chainId, isUserPools) } private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 5b1aabecc4..39ccb121ed 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme @@ -21,6 +19,7 @@ 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.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -35,12 +34,15 @@ import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.ui_core.resources.Dimens data class AllPoolsState( - val pools: List = listOf() + val userPools: List = listOf(), + val allPools: List = listOf(), + val hasExtraUserPools: Boolean = false, + val hasExtraAllPools: Boolean = false, ) interface AllPoolsScreenInterface { fun onPoolClicked(pair: StringPair) - fun onMoreClick() + fun onMoreClick(isUserPools: Boolean) } @@ -52,29 +54,53 @@ fun AllPoolsScreen( Column( modifier = Modifier.fillMaxSize(), ) { - val listState = rememberLazyListState() - MarginVertical(margin = 16.dp) - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - LazyColumn( - state = listState, - verticalArrangement = Arrangement.spacedBy(0.dp), + if (state.userPools.isNotEmpty()) { + BackgroundCorneredWithBorder( modifier = Modifier - .wrapContentHeight() + .padding(horizontal = 16.dp) ) { - item { - PoolGroupHeader(callback) + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier + .wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_your_pools), + onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } + ) + state.userPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } - items(state.pools) { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, + } + MarginVertical(margin = 16.dp) + } + if (state.allPools.isNotEmpty()) { + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier.wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_all_pools), + onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) + state.allPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } } @@ -82,7 +108,7 @@ fun AllPoolsScreen( } @Composable -private fun PoolGroupHeader(callback: AllPoolsScreenInterface) { +private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { Box(modifier = Modifier.wrapContentHeight()) { Row( modifier = Modifier @@ -94,35 +120,37 @@ private fun PoolGroupHeader(callback: AllPoolsScreenInterface) { modifier = Modifier.wrapContentHeight(), color = white, style = MaterialTheme.customTypography.header5, - text = "Your pools", + text = title, maxLines = 1, overflow = TextOverflow.Ellipsis, ) - Spacer(modifier = Modifier.weight(1f)) - Row( - modifier = Modifier - .background( - color = white08, - shape = CircleShape, - ) - .padding(all = Dimens.x1) - ) { - Text( - text = "MORE", + onMoreClick?.let { + Spacer(modifier = Modifier.weight(1f)) + Row( modifier = Modifier - .align(Alignment.CenterVertically), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.customTypography.capsTitle2, - color = white, - ) - Image( - res = R.drawable.ic_chevron_right, - modifier = Modifier - .padding(start = 8.dp) - .align(Alignment.CenterVertically) - .clickable(onClick = callback::onMoreClick) - ) + .background( + color = white08, + shape = CircleShape, + ) + .padding(all = Dimens.x1) + ) { + Text( + text = "MORE", + modifier = Modifier + .align(Alignment.CenterVertically), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.customTypography.capsTitle2, + color = white, + ) + Image( + res = R.drawable.ic_chevron_right, + modifier = Modifier + .padding(start = 8.dp) + .align(Alignment.CenterVertically) + .clickable(onClick = onMoreClick) + ) + } } } Box( @@ -156,11 +184,12 @@ val itemState = BasicPoolListItemState( ) AllPoolsScreen( state = AllPoolsState( - pools = items, + userPools = items, + allPools = items, ), callback = object : AllPoolsScreenInterface { override fun onPoolClicked(pair: StringPair) {} - override fun onMoreClick() {} + override fun onMoreClick(isUserPools: Boolean) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index 05ccca96db..98830a8438 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -149,7 +149,7 @@ fun BasicPoolListItem( @Preview @Composable private fun PreviewBasicPoolListItem() { - FearlessAppTheme { + Column { BasicPoolListItem( modifier = Modifier.background(transparent), state = BasicPoolListItemState( @@ -162,5 +162,17 @@ private fun PreviewBasicPoolListItem() { text4 = "Earn SWAP", ) ) + BasicPoolListItem( + modifier = Modifier.background(transparent), + state = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "text1", + text2 = "text2", + text3 = "text3", + text4 = "text4", + ) + ) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index c2893207ad..2ed0ba73c1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -11,7 +11,6 @@ import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 import jp.co.soramitsu.common.utils.applyFiatRate -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -28,15 +27,11 @@ import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateAddLiquidityUseCase import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.data.LiquidityData -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId -import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -93,15 +88,16 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids + val chainId = screenArgs.chainId println("!!! assetsInPoolFlow ids = $ids") val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { - it.asset.token.configuration.id == ids.first - && it.asset.token.configuration.chainId == soraMainChainId + it.asset.token.configuration.currencyId == ids.first + && it.asset.token.configuration.chainId == chainId } val secondInPair = it.firstOrNull { - it.asset.token.configuration.id == ids.second - && it.asset.token.configuration.chainId == soraMainChainId + it.asset.token.configuration.currencyId == ids.second + && it.asset.token.configuration.chainId == chainId } println("!!! assetsInPoolFlow result% $firstInPair; $secondInPair") @@ -118,22 +114,29 @@ class LiquidityAddPresenter @Inject constructor( it.first.asset.token.configuration to it.second.asset.token.configuration }.distinctUntilChanged() - val userPoolDataFlow = flowOf { getPoolDataDto() } +// val userPoolDataFlow = flowOf { getPoolDataDto() } init { } - @OptIn(FlowPreview::class) + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { -// networkFeeFlow.launchIn(coroutineScope) - - userPoolDataFlow.onEach { pool -> - val apy = pool?.reservesAccount?.let { poolsInteractor.getPoolStrategicBonusAPY(it) } - stateFlow.value = stateFlow.value.copy( - apy = apy?.toBigDecimal()?.formatPercent()?.let { "$it%" } - ) + screenArgsFlow.flatMapLatest { + val (tokenFromId, tokenToId) = it.ids + poolsInteractor.getPoolData(it.chainId, tokenFromId, tokenToId).onEach { + stateFlow.value = stateFlow.value.copy( + apy = it.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" } + ) + } }.launchIn(coroutineScope) +// userPoolDataFlow.onEach { pool -> +// val apy = pool?.reservesAccount?.let { poolsInteractor.getPoolStrategicBonusAPY(it) } +// stateFlow.value = stateFlow.value.copy( +// apy = apy?.toBigDecimal()?.formatPercent()?.let { "$it%" } +// ) +// }.launchIn(coroutineScope) + assetsInPoolFlow.onEach { (assetFrom, assetTo) -> val totalFromCrypto = assetFrom.asset.total?.formatCrypto(assetFrom.asset.token.configuration.symbol).orEmpty() val totalFromFiat = assetFrom.asset.fiatAmount?.formatFiat(assetFrom.asset.token.fiatSymbol) @@ -227,17 +230,17 @@ class LiquidityAddPresenter @Inject constructor( return stateFlow } - suspend fun getPoolDataDto(): PoolDataDto? { - val chain = accountInteractor.getChain(soraMainChainId) - val address = accountInteractor.selectedMetaAccount().address(chain) - val assets = assetsInPoolFlow.firstOrNull() - val baseTokenId = assets?.first?.asset?.token?.configuration?.currencyId - val tokenToId = assets?.second?.asset?.token?.configuration?.currencyId?.fromHex() - - if (address == null || baseTokenId == null || tokenToId == null) return null - - return poolsInteractor.getUserPoolData(address, baseTokenId, tokenToId) - } +// suspend fun getPoolDataDto(): PoolDataDto? { +// val chain = accountInteractor.getChain(soraMainChainId) +// val address = accountInteractor.selectedMetaAccount().address(chain) +// val assets = assetsInPoolFlow.firstOrNull() +// val baseTokenId = assets?.first?.asset?.token?.configuration?.currencyId +// val tokenToId = assets?.second?.asset?.token?.configuration?.currencyId?.fromHex() +// +// if (address == null || baseTokenId == null || tokenToId == null) return null +// +// return poolsInteractor.getUserPoolData(soraMainChainId, address, baseTokenId, tokenToId) +// } private suspend fun updateAmounts() { calculateAmount()?.let { targetAmount -> @@ -283,16 +286,24 @@ class LiquidityAddPresenter @Inject constructor( ) } - suspend fun calculateAmount(): BigDecimal? { + @OptIn(ExperimentalCoroutinesApi::class) + private suspend fun calculateAmount(): BigDecimal? { val assets = assetsInPoolFlow.firstOrNull() val baseAmount = if (desired == WithDesired.INPUT) enteredFromAmountFlow.value else enteredToAmountFlow.value val targetAmount = if (desired == WithDesired.INPUT) enteredToAmountFlow.value else enteredFromAmountFlow.value - val liquidity = userPoolDataFlow.firstOrNull() +// val liquidity2 = userPoolDataFlow.firstOrNull() + + val liquidity = screenArgsFlow.flatMapLatest { screenArgs -> + val chainId = screenArgs.chainId + val (tokenFromId, tokenToId) = screenArgs.ids + poolsInteractor.getPoolData(chainId, tokenFromId, tokenToId) + }.firstOrNull() + return assets?.let { (baseAsset, targetAsset) -> - val reservesFirst = baseAsset.asset.token.configuration.amountFromPlanks(liquidity?.reservesFirst.orZero()) - val reservesSecond = targetAsset.asset.token.configuration.amountFromPlanks(liquidity?.reservesSecond.orZero()) + val reservesFirst = liquidity?.basic?.baseReserves.orZero() + val reservesSecond = liquidity?.basic?.targetReserves.orZero() if (reservesSecond.isZero() || reservesSecond.isZero()) { targetAmount @@ -309,20 +320,15 @@ class LiquidityAddPresenter @Inject constructor( } } - val isPoolPairEnabled = combine( - flowOf { - val chain = accountInteractor.getChain(soraMainChainId) - val address = accountInteractor.selectedMetaAccount().address(chain)!! - address - }, - tokensInPoolFlow - ) { address, tokens -> - poolsInteractor.isPairEnabled( - tokens.first.currencyId!!, - tokens.second.currencyId!!, - accountAddress = address - ) - } + val isPoolPairEnabled = + screenArgsFlow.map { screenArgs -> + val (tokenFromId, tokenToId) = screenArgs.ids + poolsInteractor.isPairEnabled( + screenArgs.chainId, + tokenFromId, + tokenToId + ) + } val networkFeeFlow = combine( enteredFromAmountFlow, @@ -333,8 +339,8 @@ class LiquidityAddPresenter @Inject constructor( ) { amountFrom, amountTo, (baseAsset, targetAsset), slippage, pairEnabled -> val networkFee = getLiquidityNetworkFee( - baseAsset, - targetAsset, + tokenFrom = baseAsset, + tokenTo = targetAsset, tokenFromAmount = amountFrom, tokenToAmount = amountTo, pairEnabled = pairEnabled, @@ -346,12 +352,14 @@ class LiquidityAddPresenter @Inject constructor( } @OptIn(ExperimentalCoroutinesApi::class) - private val feeInfoViewStateFlow: Flow = flowOf { - requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> + private val feeInfoViewStateFlow: Flow = + screenArgsFlow.map { screenArgs -> + val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) + screenArgs.chainId to utilityAssetId + }.flatMapLatest { (chainId, utilityAssetId) -> combine( networkFeeFlow, - walletInteractor.assetFlow(soraMainChainId, utilityAssetId) + walletInteractor.assetFlow(chainId, utilityAssetId) ) { networkFee, utilityAsset -> val tokenSymbol = utilityAsset.token.configuration.symbol val tokenFiatRate = utilityAsset.token.fiatRate @@ -373,9 +381,12 @@ class LiquidityAddPresenter @Inject constructor( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal { - val soraChain = walletInteractor.getChain(soraMainChainId) + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + + val soraChain = walletInteractor.getChain(chainId) val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val result = poolsInteractor.calcAddLiquidityNetworkFee( + chainId, address, tokenFrom, tokenTo, @@ -399,8 +410,10 @@ class LiquidityAddPresenter @Inject constructor( println("!!! should setButtonLoading(true)") coroutinesStore.uiScope.launch { - val utilityAssetId = requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) - val utilityAmount = walletInteractor.getCurrentAsset(soraMainChainId, utilityAssetId).total + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + + val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) + val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total val feeAmount = networkFeeFlow.firstOrNull().orZero() val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch @@ -427,7 +440,7 @@ class LiquidityAddPresenter @Inject constructor( } val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountFrom, amountTo, stateFlow.value.apy.orEmpty()) + internalPoolsRouter.openAddLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo, stateFlow.value.apy.orEmpty()) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 3babd7e209..78cc726f50 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -6,7 +6,6 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.utils.applyFiatRate -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -18,7 +17,6 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -58,14 +56,15 @@ class LiquidityAddConfirmPresenter @Inject constructor( val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids + val chainId = screenArgs.chainId val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { - it.asset.token.configuration.id == ids.first - && it.asset.token.configuration.chainId == soraMainChainId + it.asset.token.configuration.currencyId == ids.first + && it.asset.token.configuration.chainId == chainId } val secondInPair = it.firstOrNull { - it.asset.token.configuration.id == ids.second - && it.asset.token.configuration.chainId == soraMainChainId + it.asset.token.configuration.currencyId == ids.second + && it.asset.token.configuration.chainId == chainId } if (firstInPair == null || secondInPair == null) { return@mapNotNull null @@ -80,20 +79,16 @@ class LiquidityAddConfirmPresenter @Inject constructor( it.first.asset.token to it.second.asset.token }.distinctUntilChanged() - val isPoolPairEnabled = combine( - flowOf { - val chain = accountInteractor.getChain(soraMainChainId) - val address = accountInteractor.selectedMetaAccount().address(chain)!! - address - }, - tokensInPoolFlow - ) { address, tokens -> - poolsInteractor.isPairEnabled( - tokens.first.configuration.currencyId!!, - tokens.second.configuration.currencyId!!, - accountAddress = address - ) - } + val isPoolPairEnabled = + screenArgsFlow.map { screenArgs -> + val (tokenFromId, tokenToId) = screenArgs.ids + poolsInteractor.isPairEnabled( + screenArgs.chainId, + tokenFromId, + tokenToId + ) + } + init { @@ -124,7 +119,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( ) }.launchIn(coroutineScope) - createFeeInfoViewState().onEach { + feeInfoViewStateFlow.onEach { stateFlow.value = stateFlow.value.copy( feeInfo = it ) @@ -132,47 +127,46 @@ class LiquidityAddConfirmPresenter @Inject constructor( } - @OptIn(ExperimentalCoroutinesApi::class) - private fun createFeeInfoViewState(): Flow { - val networkFeeHelperFlow = combine( - screenArgsFlow, - tokensInPoolFlow, - stateSlippage, - isPoolPairEnabled + val networkFeeFlow = combine( + screenArgsFlow, + tokensInPoolFlow, + stateSlippage, + isPoolPairEnabled + ) + { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> + val networkFee = getLiquidityNetworkFee( + tokenFrom = baseAsset.configuration, + tokenTo = targetAsset.configuration, + tokenFromAmount = screenArgs.amountFrom, + tokenToAmount = screenArgs.amountTo, + pairEnabled = pairEnabled, + pairPresented = true, //pairPresented, + slippageTolerance = slippage ) - { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> - val networkFee = getLiquidityNetworkFee( - baseAsset.configuration, - targetAsset.configuration, - tokenFromAmount = screenArgs.amountFrom, - tokenToAmount = screenArgs.amountTo, - pairEnabled = pairEnabled, - pairPresented = true, //pairPresented, - slippageTolerance = slippage - ) - networkFee - } + println("!!!! networkFeeFlow emit $networkFee") + networkFee + } - return flowOf { - requireNotNull(chainsRepository.getChain(soraMainChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = + screenArgsFlow.map { screenArgs -> + val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) + screenArgs.chainId to utilityAssetId + }.flatMapLatest { (chainId, utilityAssetId) -> combine( - networkFeeHelperFlow, - walletInteractor.assetFlow(soraMainChainId, utilityAssetId) + networkFeeFlow, + walletInteractor.assetFlow(chainId, utilityAssetId) ) { networkFee, utilityAsset -> val tokenSymbol = utilityAsset.token.configuration.symbol val tokenFiatRate = utilityAsset.token.fiatRate val tokenFiatSymbol = utilityAsset.token.fiatSymbol - return@combine FeeInfoViewState( + FeeInfoViewState( feeAmount = networkFee.formatCryptoDetail(tokenSymbol), feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), ) } } - } - - private suspend fun getLiquidityNetworkFee( tokenFrom: Asset, @@ -183,9 +177,11 @@ class LiquidityAddConfirmPresenter @Inject constructor( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal { - val soraChain = walletInteractor.getChain(soraMainChainId) + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + val soraChain = walletInteractor.getChain(chainId) val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val result = poolsInteractor.calcAddLiquidityNetworkFee( + chainId, user, tokenFrom, tokenTo, @@ -201,6 +197,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( override fun onConfirmClick() { println("!!! LiquidityAddConfirm onConfirmClick") coroutinesStore.ioScope.launch { + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch val tokento = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch val amountFrom = screenArgsFlow.firstOrNull()?.amountFrom.orZero() @@ -211,6 +208,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( try { println("!!! LiquidityAddConfirm onConfirmClick run observeAddLiquidity") result = poolsInteractor.observeAddLiquidity( + chainId, tokenFrom, tokento, amountFrom, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index f8acf31382..9e218e3db9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -4,23 +4,27 @@ import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.formatPercent import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch @@ -51,21 +55,22 @@ class PoolDetailsPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { - screenArgsFlow.onEach { - requestPoolDetails(it.ids)?.let { - stateFlow.value = it + screenArgsFlow.flatMapLatest { + observePoolDetails(it.chainId, it.ids).onEach { + stateFlow.value = it.mapToState() } +// requestPoolDetails(it.ids)?.let { +// stateFlow.value = it +// } }.launchIn(coroutineScope) } override fun onSupplyLiquidityClick() { coroutinesStore.ioScope.launch { - screenArgsFlow.map { - it.ids - }.firstOrNull()?.let { ids -> - internalPoolsRouter.openAddLiquidityScreen(ids) - } + val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + internalPoolsRouter.openAddLiquidityScreen(chainId, ids) } } @@ -73,25 +78,33 @@ class PoolDetailsPresenter @Inject constructor( println("!!! onRemoveLiquidityClick") } + suspend fun observePoolDetails(chainId: ChainId, ids: StringPair): Flow { + val (tokenFromId, tokenToId) = ids +// val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() +// val address = accountRepository.getSelectedAccount(payload.chainId).address + + return poolsInteractor.getPoolData(chainId, tokenFromId, tokenToId) + } + suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { - val soraChain = accountInteractor.getChain(soraMainChainId) + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return null + + val soraChain = accountInteractor.getChain(chainId) val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val baseAsset = soraChain.assets.firstOrNull { it.id == ids.first } val targetAsset = soraChain.assets.firstOrNull { it.id == ids.second } val baseTokenId = baseAsset?.currencyId ?: error("No currency for Asset ${baseAsset?.symbol}") val targetTokenId = targetAsset?.currencyId ?: error("No currency for Asset ${targetAsset?.symbol}") - val retur = poolsInteractor.getUserPoolData(address, baseTokenId, targetTokenId.fromHex())?.let { + val result = poolsInteractor.getUserPoolData(chainId, address, baseTokenId, targetTokenId.fromHex())?.let { PoolDetailsState( - originTokenIcon = GradientIconData(baseAsset.iconUrl, null), - destinationTokenIcon = GradientIconData(targetAsset.iconUrl, null), - fromTokenSymbol = baseAsset.symbol, - toTokenSymbol = targetAsset.symbol, + assetFrom = baseAsset, + assetTo = targetAsset, tvl = null, apy = null ) } - return retur + return result } private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = @@ -100,4 +113,17 @@ class PoolDetailsPresenter @Inject constructor( this.token.configuration.id.lowercase().contains(filter.lowercase()) -} \ No newline at end of file +} + +private fun CommonPoolData.mapToState(): PoolDetailsState { + return PoolDetailsState( + assetFrom = basic.baseToken.token.configuration, + assetTo = basic.targetToken?.token?.configuration, + pooledBaseAmount = user?.basePooled?.formatCrypto(basic.baseToken.token.configuration.symbol).orEmpty(), + pooledBaseFiat = user?.basePooled?.applyFiatRate(basic.baseToken.token.fiatRate)?.formatFiat(basic.baseToken.token.fiatSymbol).orEmpty(), + pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.token?.configuration?.symbol).orEmpty(), + pooledTargetFiat = user?.targetPooled?.applyFiatRate(basic.targetToken?.token?.fiatRate)?.formatFiat(basic.targetToken?.token?.fiatSymbol).orEmpty(), + tvl = basic.tvl?.formatFiat(basic.baseToken.token.fiatSymbol), + apy = "${basic.sbapy?.toBigDecimal()?.formatPercent()}%" + ) +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index d6ef9cc88e..119ae4c8ac 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -6,7 +6,9 @@ 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.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon @@ -15,35 +17,42 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import coil.compose.AsyncImage import jp.co.soramitsu.common.compose.component.AccentButton -import jp.co.soramitsu.common.compose.component.DoubleGradientIcon -import jp.co.soramitsu.common.compose.component.GradientIconState +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.GrayButton -import jp.co.soramitsu.common.compose.component.InfoTable +import jp.co.soramitsu.common.compose.component.InfoTableItem +import jp.co.soramitsu.common.compose.component.InfoTableItemAsset import jp.co.soramitsu.common.compose.component.MarginHorizontal import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.TitleIconValueState import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.component.getImageRequest import jp.co.soramitsu.common.compose.theme.customColors import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.wallet.impl.presentation.cross_chain.confirm.GradientIconData data class PoolDetailsState( - val originTokenIcon: GradientIconData? = null, - val destinationTokenIcon: GradientIconData? = null, - val fromTokenSymbol: String? = null, - val toTokenSymbol: String? = null, + val assetFrom: Asset? = null, + val assetTo: Asset? = null, + val pooledBaseAmount: String = "", + val pooledBaseFiat: String = "", + val pooledTargetAmount: String = "", + val pooledTargetFiat: String = "", val tvl: String? = null, val apy: String? = null ) interface PoolDetailsCallbacks { - fun onSupplyLiquidityClick() fun onRemoveLiquidityClick() } @@ -67,10 +76,24 @@ fun PoolDetailsScreen( ) { MarginVertical(margin = 16.dp) - DoubleGradientIcon( - leftImage = provideGradientIconState(state.originTokenIcon), - rightImage = provideGradientIconState(state.destinationTokenIcon) - ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetFrom?.iconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = 7.dp) + .zIndex(1f) + ) + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetTo?.iconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = (-7).dp) + .zIndex(0f) + ) + } Row( modifier = Modifier @@ -80,7 +103,7 @@ fun PoolDetailsScreen( horizontalArrangement = Arrangement.Center ) { Text( - text = state.fromTokenSymbol.orEmpty(), + text = state.assetFrom?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -98,7 +121,7 @@ fun PoolDetailsScreen( MarginHorizontal(margin = 8.dp) Text( - text = state.toTokenSymbol.orEmpty(), + text = state.assetTo?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -106,24 +129,48 @@ fun PoolDetailsScreen( ) } - InfoTable( - modifier = Modifier.padding(top = 24.dp), - items = listOf( - TitleValueViewState( - title = "TVL", - value = state.tvl - ), - TitleValueViewState( - title = "Strategic bonus APY", - value = state.apy, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) - ), - TitleValueViewState( - title = "Rewards payout in", - value = "pswap" + MarginVertical(margin = 24.dp) + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + ) { + Column { + InfoTableItem(TitleValueViewState("TVL", state.tvl)) + InfoTableItem( + TitleValueViewState( + title = "Strategic bonus APY", + value = state.apy, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) + ) ) - ) - ) + InfoTableItemAsset( + TitleIconValueState( + title = "Rewards payout in", + iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", + value = "PSWAP" + ) + ) + if (state.pooledBaseAmount.isNotEmpty()) { + InfoTableItem( + TitleValueViewState( + title = stringResource(id = R.string.pl_your_pooled_format, state.assetFrom?.symbol?.uppercase().orEmpty()), + value = state.pooledBaseAmount, + additionalValue = state.pooledBaseFiat + ) + ) + } + if (state.pooledTargetAmount.isNotEmpty()) { + InfoTableItem( + TitleValueViewState( + title = stringResource(id = R.string.pl_your_pooled_format, state.assetTo?.symbol?.uppercase().orEmpty()), + value = state.pooledTargetAmount, + additionalValue = state.pooledTargetFiat + ) + ) + + } + } + } MarginVertical(margin = 24.dp) @@ -135,46 +182,28 @@ fun PoolDetailsScreen( text = "Supply liquidity", onClick = callbacks::onSupplyLiquidityClick ) - MarginVertical(margin = 8.dp) - GrayButton( - modifier = Modifier - .height(48.dp) - .padding(horizontal = 16.dp) - .fillMaxWidth(), - text = "Remove liquidity", - onClick = callbacks::onRemoveLiquidityClick - ) - MarginVertical(margin = 8.dp) + if (state.pooledBaseAmount.isNotEmpty()) { + GrayButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Remove liquidity", + onClick = callbacks::onRemoveLiquidityClick + ) + MarginVertical(margin = 8.dp) + } } } } -private fun provideGradientIconState(gradientIconData: GradientIconData?): GradientIconState { - val url = gradientIconData?.url - return if (url == null) { - GradientIconState.Local( - res = R.drawable.ic_fearless_logo - ) - } else { - GradientIconState.Remote( - url = url, - color = gradientIconData.color - ) - } -} - - @Preview @Composable private fun PreviewPoolDetailsScreen() { PoolDetailsScreen( state = PoolDetailsState( - originTokenIcon = GradientIconData(null, null), - destinationTokenIcon = GradientIconData(null, null), - fromTokenSymbol = "XOR", - toTokenSymbol = "ETH", apy = "23.3%", tvl = "$34.999 TVL", ), diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 8527b56af4..2bf8033b78 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -40,20 +41,26 @@ class PoolListPresenter @Inject constructor( .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) + val chainFlow = screenArgsFlow.map { screenArgs -> + chainsRepository.getChain(screenArgs.chainId) + } - val pools = combine( - flowOf { poolsInteractor.getBasicPools() }, - enteredAssetQueryFlow - ) { pools, query -> - pools.filter { - it.isFilterMatch(query) - }.sortedWith { o1, o2 -> - compareNullDesc(o1.tvl, o2.tvl) + val pools = screenArgsFlow.flatMapLatest { screenArgs -> + combine( + flowOf { poolsInteractor.getBasicPools(screenArgs.chainId) }, + enteredAssetQueryFlow + ) { pools, query -> + pools.filter { + it.isFilterMatch(query) + }.sortedWith { o1, o2 -> + compareNullDesc(o1.tvl, o2.tvl) + } + }.map { + it.mapNotNull(BasicPoolData::toListItemState) } - }.map { - it.map(BasicPoolData::toListItemState) } + init { } @@ -73,8 +80,9 @@ class PoolListPresenter @Inject constructor( override fun onPoolClicked(pair: StringPair) { - val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") - internalPoolsRouter.openDetailsPoolScreen(xorPswap) +// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return + internalPoolsRouter.openDetailsPoolScreen(chainId, pair) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index 093145e382..570c8fef94 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -3,7 +3,6 @@ package jp.co.soramitsu.liquiditypools.impl.usecase import java.math.BigDecimal import javax.inject.Inject import jp.co.soramitsu.common.utils.orZero -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.wallet.api.domain.TransferValidationResult import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index 405604a8d2..3218c9295c 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.Flow import java.math.BigInteger import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair interface PolkaswapRepository { @@ -59,22 +59,24 @@ interface PolkaswapRepository { desired: WithDesired ): Result - suspend fun getBasicPools(): List - - suspend fun getPoolOfAccount( - address: String?, - tokenFromId: String, - tokenToId: String, - chainId: String - ): CommonUserPoolData? + suspend fun getBasicPools(chainId: ChainId): List +// suspend fun getPoolOfAccount( +// address: String?, +// tokenFromId: String, +// tokenToId: String, +// chainId: String +// ): CommonUserPoolData? +// suspend fun getUserPoolData( + chainId: ChainId, address: String, baseTokenId: String, tokenId: ByteArray ): PoolDataDto? suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, address: String, tokenFrom: Asset, tokenTo: Asset, @@ -85,11 +87,12 @@ interface PolkaswapRepository { slippageTolerance: Double ): BigDecimal? - suspend fun getPoolBaseTokenDexId(tokenId: String?): Int - suspend fun updatePoolsSbApy() + suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int +// suspend fun updatePoolsSbApy() fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? suspend fun observeAddLiquidity( + chainId: ChainId, address: String, keypair: Keypair, tokenFrom: Asset, @@ -101,4 +104,11 @@ interface PolkaswapRepository { slippageTolerance: Double ): Result? + suspend fun updateAccountPools(chainId: ChainId, address: String) + suspend fun updateBasicPools(chainId: ChainId) + + fun subscribePools(address: String): Flow> + fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow + + } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt index 2257c133ef..49560b2f8c 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt @@ -63,6 +63,6 @@ interface PolkaswapInteractor { suspend fun getAvailableDexesForPair(tokenFromId: String, tokenToId: String, dexes: List): List fun observeHasReadDisclaimer(): Flow - suspend fun updatePoolsSbApy() +// suspend fun updatePoolsSbApy() fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt index ea3be99287..b4a40d74d0 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt @@ -17,7 +17,7 @@ data class CommonPoolData( } data class UserPoolData( - val address: String?, +// val address: String?, val basePooled: BigDecimal, val targetPooled: BigDecimal, val poolShare: Double, diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index b8977f241f..1fa2c423c6 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -21,11 +21,15 @@ import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.core.utils.utilityAsset -import jp.co.soramitsu.polkaswap.api.data.LiquidityData +import jp.co.soramitsu.coredb.dao.PoolDao +import jp.co.soramitsu.coredb.model.BasicPoolLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocalNullable +import jp.co.soramitsu.coredb.model.UserPoolLocal import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired @@ -44,7 +48,6 @@ import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.runtime.network.subscriptionFlowCatching import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair @@ -53,7 +56,6 @@ import jp.co.soramitsu.shared_utils.extensions.toHexString import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex -import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder import jp.co.soramitsu.shared_utils.runtime.metadata.module import jp.co.soramitsu.shared_utils.runtime.metadata.storage import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey @@ -74,13 +76,16 @@ import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStora import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull class PolkaswapRepositoryImpl @Inject constructor( private val remoteConfigFetcher: RemoteConfigFetcher, @@ -91,6 +96,7 @@ class PolkaswapRepositoryImpl @Inject constructor( private val accountRepository: AccountRepository, private val walletRepository: WalletRepository, private val blockExplorerManager: BlockExplorerManager, + private val poolDao: PoolDao, ) : PolkaswapRepository { override suspend fun getAvailableDexes(chainId: ChainId): List { @@ -270,14 +276,14 @@ class PolkaswapRepositoryImpl @Inject constructor( baseTokenId.mapCodeToken(), ) - suspend fun getStorageHex(storageKey: String): String? = - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + suspend fun getStorageHex(chainId: ChainId, storageKey: String): String? = + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = pojo(), ).result - suspend fun getStateKeys(partialKey: String): List = - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + suspend fun getStateKeys(chainId: ChainId, partialKey: String): List = + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = StateKeys(listOf(partialKey)), mapper = pojoList(), ).result ?: emptyList() @@ -294,10 +300,11 @@ class PolkaswapRepositoryImpl @Inject constructor( } suspend fun getPoolReserveAccount( + chainId: ChainId, baseTokenId: String, tokenId: ByteArray ): ByteArray? { - val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storageKey = runtimeOrNull?.metadata ?.module(Modules.POOL_XYK) ?.storage("Properties")?.storageKey( @@ -307,7 +314,7 @@ class PolkaswapRepositoryImpl @Inject constructor( ) ?: return null - return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = scale(PoolPropertiesResponse), ) @@ -324,14 +331,15 @@ class PolkaswapRepositoryImpl @Inject constructor( } suspend fun getPoolTotalIssuances( + chainId: ChainId, reservesAccountId: ByteArray, ): BigInteger? { - val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) ?.storage("TotalIssuances") ?.storageKey(runtimeOrNull, reservesAccountId) ?: return null - return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = scale(TotalIssuance), ) @@ -347,21 +355,21 @@ class PolkaswapRepositoryImpl @Inject constructor( return tempApy } - override suspend fun updatePoolsSbApy() { - println("!!! call blockExplorerManager.updatePoolsSbApy()") - blockExplorerManager.updatePoolsSbApy() - } +// private suspend fun updatePoolsSbApy() { +// println("!!! call blockExplorerManager.updatePoolsSbApy()") +// blockExplorerManager.updatePoolsSbApy() +// } - override suspend fun getBasicPools(): List { + override suspend fun getBasicPools(chainId: ChainId): List { println("!!! getBasicPools() start") - val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storage = runtimeOrNull?.metadata ?.module(Modules.POOL_XYK) ?.storage("Reserves") val list = mutableListOf() - val soraChain = chainRegistry.getChain(soraMainChainId) + val soraChain = chainRegistry.getChain(chainId) val wallet = accountRepository.getSelectedMetaAccount() @@ -383,19 +391,20 @@ class PolkaswapRepositoryImpl @Inject constructor( val currencyId = asset.token.configuration.currencyId val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } key?.let { - getStateKeys(it).forEach { storageKey -> + getStateKeys(chainId, it).forEach { storageKey -> val targetToken = storageKey.assetIdFromKey() - getStorageHex(storageKey)?.let { storageHex -> + getStorageHex(chainId, storageKey)?.let { storageHex -> storage?.type?.value ?.fromHex(runtimeOrNull, storageHex) ?.safeCast>()?.let { reserves -> val reserveAccount = getPoolReserveAccount( + chainId, currencyId, targetToken.fromHex() ) val total = reserveAccount?.let { - getPoolTotalIssuances(it) + getPoolTotalIssuances(chainId, it) }?.let { mapBalance(it, asset.token.configuration.precision) } @@ -416,8 +425,6 @@ class PolkaswapRepositoryImpl @Inject constructor( list.add( element ) - // todo remove - if (list.size > 0) return list } } } @@ -430,11 +437,12 @@ class PolkaswapRepositoryImpl @Inject constructor( } private fun subscribeAccountPoolProviders( + chainId: ChainId, address: String, reservesAccount: ByteArray, ): Flow = flow { val poolProvidersKey = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + chainRegistry.getRuntimeOrNull(chainId)?.let { it.metadata.module(Modules.POOL_XYK) .storage("TotalIssuances") .storageKey( @@ -443,7 +451,7 @@ class PolkaswapRepositoryImpl @Inject constructor( address.toAccountId() ) } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") - val poolProvidersFlow = chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + val poolProvidersFlow = chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( SubscribeStorageRequest(poolProvidersKey), "state_unsubscribeStorage", ).map { @@ -455,18 +463,19 @@ class PolkaswapRepositoryImpl @Inject constructor( } override suspend fun getUserPoolData( + chainId: ChainId, address: String, baseTokenId: String, tokenId: ByteArray ): PoolDataDto? { - val reserves = getPairWithXorReserves(baseTokenId, tokenId) + val reserves = getPairWithXorReserves(chainId, baseTokenId, tokenId) val totalIssuanceAndProperties = - getPoolTotalIssuanceAndProperties(baseTokenId, tokenId, address) + getPoolTotalIssuanceAndProperties(chainId, baseTokenId, tokenId, address) if (reserves == null || totalIssuanceAndProperties == null) { return null } - val reservesAccount = chainRegistry.getChain(soraMainChainId).addressOf(totalIssuanceAndProperties.third) + val reservesAccount = chainRegistry.getChain(chainId).addressOf(totalIssuanceAndProperties.third) return PoolDataDto( baseTokenId, @@ -480,6 +489,7 @@ class PolkaswapRepositoryImpl @Inject constructor( } override suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, address: String, tokenFrom: Asset, tokenTo: Asset, @@ -491,8 +501,8 @@ class PolkaswapRepositoryImpl @Inject constructor( ): BigDecimal? { val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenFromAmount, slippageTolerance) val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenToAmount, slippageTolerance) - val dexId = getPoolBaseTokenDexId(tokenFrom.currencyId) - val chain = chainRegistry.getChain(soraMainChainId) + val dexId = getPoolBaseTokenDexId(chainId, tokenFrom.currencyId) + val chain = chainRegistry.getChain(chainId) val fee = extrinsicService.estimateFee(chain) { liquidityAdd( dexId, @@ -511,26 +521,26 @@ class PolkaswapRepositoryImpl @Inject constructor( return feeToken?.amountFromPlanks(fee) } - override suspend fun getPoolBaseTokenDexId(tokenId: String?): Int { - return getPoolBaseTokens().first { + override suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int { + return getPoolBaseTokens(chainId).first { it.second == tokenId }.first } - private suspend fun getPoolBaseTokens(): List> { - val runtimeSnapshot = chainRegistry.getRuntimeOrNull(soraMainChainId) + private suspend fun getPoolBaseTokens(chainId: ChainId): List> { + val runtimeSnapshot = chainRegistry.getRuntimeOrNull(chainId) val metadataStorage = runtimeSnapshot?.metadata ?.module("DEXManager") ?.storage("DEXInfos") val partialKey = metadataStorage ?.storageKey() ?: error("getPoolBaseTokenDexId storageKey not supported") - return chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = StateKeys(listOf(partialKey)), mapper = pojoList().nonNull() ).let { storageKeys -> storageKeys.mapNotNull { storageKey -> - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = pojo().nonNull() ).let { storage -> @@ -554,15 +564,18 @@ class PolkaswapRepositoryImpl @Inject constructor( fun String.takeInt32() = uint32.fromHex(this.takeLast(8)).toInt() private suspend fun getPoolTotalIssuanceAndProperties( + chainId: ChainId, baseTokenId: String, tokenId: ByteArray, address: String ): Triple? { - return getPoolReserveAccount(baseTokenId, tokenId)?.let { account -> + return getPoolReserveAccount(chainId, baseTokenId, tokenId)?.let { account -> getPoolTotalIssuances( + chainId, account )?.let { val provider = getPoolProviders( + chainId, account, address ) @@ -576,11 +589,12 @@ class PolkaswapRepositoryImpl @Inject constructor( } private suspend fun getPoolProviders( + chainId: ChainId, reservesAccountId: ByteArray, currentAddress: String ): BigInteger { val storageKey = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + chainRegistry.getRuntimeOrNull(chainId)?.let { it.metadata.module(Modules.POOL_XYK) .storage("PoolProviders").storageKey( it, @@ -589,7 +603,7 @@ class PolkaswapRepositoryImpl @Inject constructor( ) } ?: return BigInteger.ZERO return runCatching { - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = scale(PoolProviders), ) @@ -610,14 +624,15 @@ class PolkaswapRepositoryImpl @Inject constructor( } private suspend fun getPairWithXorReserves( + chainId: ChainId, baseTokenId: String, tokenId: ByteArray ): Pair? { val storageKey = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.reservesKey(baseTokenId, tokenId) + chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) ?: return null return try { - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = scale(ReservesResponse), ) @@ -633,114 +648,114 @@ class PolkaswapRepositoryImpl @Inject constructor( } } - override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { - return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { - it.user.address == address - } - } - - suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { -// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) -// val socketService = chainRegistry.getConnection(chainId).socketService -// socketService.executeAsyncCatching() - val tokensPair: List>>? = address?.let { - getUserPoolsTokenIds(it) - } - - val pools = mutableListOf() - - tokensPair?.forEach { (baseTokenId, tokensId) -> - tokensId.mapNotNull { tokenId -> - getUserPoolData(address, baseTokenId, tokenId) - }.forEach pool@{ poolDataDto -> - val metaId = accountRepository.getSelectedLightMetaAccount().id - val assets = walletRepository.getAssets(metaId) - val token = assets.firstOrNull { - it.token.configuration.currencyId == poolDataDto.assetId - } ?: return@pool - val baseToken = assets.firstOrNull { - it.token.configuration.currencyId == baseTokenId - } ?: return@pool - val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 - val tokenPrecision = token?.token?.configuration?.precision ?: 0 - - val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) - - val basePooled = PolkaswapFormulas.calculatePooledValue( - mapBalance( - poolDataDto.reservesFirst, - xorPrecision - ), - mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision - ), - mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - baseToken?.token?.configuration?.precision, - ) - val secondPooled = PolkaswapFormulas.calculatePooledValue( - mapBalance( - poolDataDto.reservesSecond, - tokenPrecision - ), - mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision - ), - mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - token?.token?.configuration?.precision, - ) - val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( - mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision - ), - mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - ) - val userPoolData = CommonUserPoolData( - basic = BasicPoolData( - baseToken = baseToken, - targetToken = token, - baseReserves = mapBalance( - poolDataDto.reservesFirst, - xorPrecision - ), - targetReserves = mapBalance( - poolDataDto.reservesSecond, - tokenPrecision - ), - totalIssuance = mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - reserveAccount = poolDataDto.reservesAccount, - sbapy = apy, - ), - user = UserPoolData( - address = address, - basePooled = basePooled, - targetPooled = secondPooled, - share, - mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision - ), - ), - ) - pools.add(userPoolData) - } - } - return pools - } +// override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { +// return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { +// it.user.address == address +// } +// } + +// suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { +//// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) +//// val socketService = chainRegistry.getConnection(chainId).socketService +//// socketService.executeAsyncCatching() +// val tokensPair: List>>? = address?.let { +// getUserPoolsTokenIds(it) +// } +// +// val pools = mutableListOf() +// +// tokensPair?.forEach { (baseTokenId, tokensId) -> +// tokensId.mapNotNull { tokenId -> +// getUserPoolData(address, baseTokenId, tokenId) +// }.forEach pool@{ poolDataDto -> +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// val token = assets.firstOrNull { +// it.token.configuration.currencyId == poolDataDto.assetId +// } ?: return@pool +// val baseToken = assets.firstOrNull { +// it.token.configuration.currencyId == baseTokenId +// } ?: return@pool +// val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 +// val tokenPrecision = token?.token?.configuration?.precision ?: 0 +// +// val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) +// +// val basePooled = PolkaswapFormulas.calculatePooledValue( +// mapBalance( +// poolDataDto.reservesFirst, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// baseToken?.token?.configuration?.precision, +// ) +// val secondPooled = PolkaswapFormulas.calculatePooledValue( +// mapBalance( +// poolDataDto.reservesSecond, +// tokenPrecision +// ), +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// token?.token?.configuration?.precision, +// ) +// val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// ) +// val userPoolData = CommonUserPoolData( +// basic = BasicPoolData( +// baseToken = baseToken, +// targetToken = token, +// baseReserves = mapBalance( +// poolDataDto.reservesFirst, +// xorPrecision +// ), +// targetReserves = mapBalance( +// poolDataDto.reservesSecond, +// tokenPrecision +// ), +// totalIssuance = mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// reserveAccount = poolDataDto.reservesAccount, +// sbapy = apy, +// ), +// user = UserPoolData( +//// address = address, +// basePooled = basePooled, +// targetPooled = secondPooled, +// share, +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// ), +// ) +// pools.add(userPoolData) +// } +// } +// return pools +// } fun RuntimeSnapshot.reservesKey(baseTokenId: String, tokenId: ByteArray): String = this.metadata.module(Modules.POOL_XYK) @@ -762,15 +777,16 @@ class PolkaswapRepositoryImpl @Inject constructor( } private fun subscribeToPoolData( + chainId: ChainId, baseTokenId: String, tokenId: ByteArray, reservesAccount: ByteArray, ): Flow = flow { val reservesKey = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.reservesKey(baseTokenId, tokenId) + chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) val reservesFlow = reservesKey?.let { - chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( SubscribeStorageRequest(reservesKey), "state_unsubscribeStorage", ).map { @@ -781,13 +797,13 @@ class PolkaswapRepositoryImpl @Inject constructor( } ?: emptyFlow() val totalIssuanceKey = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.let { + chainRegistry.getRuntimeOrNull(chainId)?.let { it.metadata.module(Modules.POOL_XYK) .storage("TotalIssuances") .storageKey(it, reservesAccount) } val totalIssuanceFlow = totalIssuanceKey?.let { - chainRegistry.getConnection(soraMainChainId).socketService.subscriptionFlowCatching( + chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( SubscribeStorageRequest(totalIssuanceKey), "state_unsubscribeStorage", ).map { @@ -813,11 +829,10 @@ class PolkaswapRepositoryImpl @Inject constructor( ?.toByteArray() - suspend fun getUserPoolsTokenIdsKeys(address: String): List { - val accountPoolsKey = chainRegistry.getRuntimeOrNull(soraMainChainId)?.accountPoolsKey(address) - chainRegistry.awaitConnection(soraMainChainId).socketService + suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { + val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) return runCatching { - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = StateKeys(listOfNotNull(accountPoolsKey)), mapper = pojoList().nonNull() ) @@ -829,21 +844,22 @@ class PolkaswapRepositoryImpl @Inject constructor( } suspend fun getUserPoolsTokenIds( + chainId: ChainId, address: String ): List>> { return runCatching { - val storageKeys = getUserPoolsTokenIdsKeys(address) + val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) storageKeys.map { storageKey -> - chainRegistry.awaitConnection(soraMainChainId).socketService.executeAsync( + chainRegistry.awaitConnection(chainId).socketService.executeAsync( request = GetStorageRequest(listOf(storageKey)), mapper = pojo().nonNull(), ) .let { storage -> val storageType = - chainRegistry.getRuntimeOrNull(soraMainChainId)?.metadata?.module(Modules.POOL_XYK) + chainRegistry.getRuntimeOrNull(chainId)?.metadata?.module(Modules.POOL_XYK) ?.storage("AccountPools")?.type?.value!! val storageRawData = - storageType.fromHex(chainRegistry.getRuntimeOrNull(soraMainChainId)!!, storage) + storageType.fromHex(chainRegistry.getRuntimeOrNull(chainId)!!, storage) val tokens: List = if (storageRawData is List<*>) { storageRawData.filterIsInstance() .mapNotNull { struct -> @@ -862,6 +878,7 @@ class PolkaswapRepositoryImpl @Inject constructor( } override suspend fun observeAddLiquidity( + chainId: ChainId, address: String, keypair: Keypair, tokenFrom: Asset, @@ -875,8 +892,8 @@ class PolkaswapRepositoryImpl @Inject constructor( // ): Pair? { val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountFrom, slippageTolerance) val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTo, slippageTolerance) - val dexId = getPoolBaseTokenDexId(tokenFrom.currencyId) - val soraChain = accountRepository.getChain(soraMainChainId) + val dexId = getPoolBaseTokenDexId(chainId, tokenFrom.currencyId) + val soraChain = accountRepository.getChain(chainId) val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId val baseTokenId = tokenFrom.currencyId @@ -915,9 +932,214 @@ class PolkaswapRepositoryImpl @Inject constructor( } } + override suspend fun updateAccountPools(chainId: ChainId, address: String) { + println("!!! call blockExplorerManager.updatePoolsSbApy()") + blockExplorerManager.updatePoolsSbApy() + + val pools = mutableListOf() + + val assets = chainRegistry.getChain(chainId).assets + + val tokenIds = getUserPoolsTokenIds(chainId, address) + tokenIds.forEach { (baseTokenId, tokensId) -> + + val baseToken = assets.firstOrNull { + it.currencyId == baseTokenId + } ?: return@forEach + + val xorPrecision = baseToken.precision + + tokensId.mapNotNull { tokenId -> + getUserPoolData(chainId, address, baseTokenId, tokenId) + }.forEach pool@{ poolDataDto -> + val token = assets.firstOrNull { + it.currencyId == poolDataDto.assetId + } ?: return@pool + val tokenPrecision = token.precision + + val basicPoolLocal = BasicPoolLocal( + tokenIdBase = baseTokenId, + tokenIdTarget = poolDataDto.assetId, + reserveBase = mapBalance( + poolDataDto.reservesFirst, + xorPrecision + ), + reserveTarget = mapBalance( + poolDataDto.reservesSecond, + tokenPrecision + ), + totalIssuance = mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + reservesAccount = poolDataDto.reservesAccount, + ) + + val userPoolLocal = UserPoolLocal( + accountAddress = address, + userTokenIdBase = baseTokenId, + userTokenIdTarget = poolDataDto.assetId, + poolProvidersBalance = mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ) + ) + + pools.add( + UserPoolJoinedLocal( + userPoolLocal = userPoolLocal, + basicPoolLocal = basicPoolLocal, + ) + ) + } + } + + poolDao.clearTable(address) + poolDao.insertBasicPools( + pools.map { + it.basicPoolLocal + } + ) + poolDao.insertUserPools( + pools.map { + it.userPoolLocal + } + ) + } + + override suspend fun updateBasicPools(chainId: ChainId) { + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) + val storage = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Reserves") + + val list = mutableListOf() + + val soraChain = chainRegistry.getChain(chainId) + val assets = soraChain.assets + + getPoolBaseTokens(chainId).forEach { (dexId, tokenId) -> + val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@forEach + val key = runtimeOrNull?.reservesKeyToken(tokenId) ?: return@forEach + + getStateKeys(chainId, key).forEach { storageKey -> + val targetToken = storageKey.assetIdFromKey() + getStorageHex(chainId, storageKey)?.let { storageHex -> + storage?.type?.value + ?.fromHex(runtimeOrNull, storageHex) + ?.safeCast>()?.let { reserves -> + + val reserveAccount = getPoolReserveAccount( + chainId, + tokenId, + targetToken.fromHex() + ) + + val total = reserveAccount?.let { + getPoolTotalIssuances(chainId, it) + }?.let { + mapBalance(it, asset.precision) + } + + val targetAsset = assets.firstOrNull { it.currencyId == targetToken } + val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" + + list.add( + BasicPoolLocal( + tokenIdBase = tokenId, + tokenIdTarget = targetToken, + reserveBase = mapBalance(reserves[0], asset.precision), + reserveTarget = mapBalance(reserves[1], asset.precision), + totalIssuance = total ?: BigDecimal.ZERO, + reservesAccount = reserveAccountAddress, + ) + ) + } + } + } + } + + val minus = poolDao.getBasicPools().filter { db -> + list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null + } + poolDao.deleteBasicPools(minus) + poolDao.insertBasicPools(list) + } + + @OptIn(FlowPreview::class) + override fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow { + return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { + mapPoolLocalToData(it) + }.debounce(500) + } + + @OptIn(FlowPreview::class) + override fun subscribePools(address: String): Flow> { +// return poolDao.subscribePoolsList(address).map { pools -> + return poolDao.subscribeAllPools(address).map { pools -> + pools.mapNotNull { poolLocal -> + mapPoolLocalToData(poolLocal) + } + }.debounce(500) + } + fun RuntimeSnapshot.accountPoolsKey(address: String): String = this.metadata.module(Modules.POOL_XYK) .storage("AccountPools") .storageKey(this, address.toAccountId()) + private suspend fun mapPoolLocalToData( + poolLocal: UserPoolJoinedLocalNullable, +// poolLocal: UserPoolJoinedLocal, + ): CommonPoolData? { + val metaId = accountRepository.getSelectedLightMetaAccount().id + val assets = walletRepository.getAssets(metaId) + + val baseToken = assets.firstOrNull { + it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdBase + } ?: return null + val token = assets.firstOrNull { + it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdTarget + } ?: return null + + val basicPoolData = BasicPoolData( + baseToken = baseToken, + targetToken = token, + baseReserves = poolLocal.basicPoolLocal.reserveBase, + targetReserves = poolLocal.basicPoolLocal.reserveTarget, + totalIssuance = poolLocal.basicPoolLocal.totalIssuance, + reserveAccount = poolLocal.basicPoolLocal.reservesAccount, + sbapy = getPoolStrategicBonusAPY(poolLocal.basicPoolLocal.reservesAccount), + ) + + val userPoolData = poolLocal.userPoolLocal?.let { userPoolLocal -> + val basePooled = PolkaswapFormulas.calculatePooledValue( + poolLocal.basicPoolLocal.reserveBase, + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + baseToken.token.configuration.precision, + ) + val secondPooled = PolkaswapFormulas.calculatePooledValue( + poolLocal.basicPoolLocal.reserveTarget, + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + token.token.configuration.precision, + ) + val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + ) + UserPoolData( + basePooled = basePooled, + targetPooled = secondPooled, + poolShare = share, + poolProvidersBalance = userPoolLocal.poolProvidersBalance, + ) + } + return CommonPoolData( + basic = basicPoolData, + user = userPoolData, + ) + } + } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 4ea026c2da..961c54582e 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -26,6 +26,7 @@ import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.IChainRegistry import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao +import jp.co.soramitsu.coredb.dao.PoolDao import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.chain.ChainSyncService import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository @@ -54,7 +55,8 @@ class PolkaswapFeatureModule { rpcCalls: RpcCalls, accountRepository: AccountRepository, walletRepository: WalletRepository, - sorablockexplorer: BlockExplorerManager + sorablockexplorer: BlockExplorerManager, + poolDao: PoolDao ): PolkaswapRepository { return PolkaswapRepositoryImpl( remoteConfigFetcher, @@ -64,7 +66,8 @@ class PolkaswapFeatureModule { rpcCalls, accountRepository, walletRepository, - sorablockexplorer + sorablockexplorer, + poolDao ) } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt index fa6b064e5e..2fd73d3e60 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt @@ -269,9 +269,9 @@ class PolkaswapInteractorImpl @Inject constructor( }.filter { it.second }.map { it.first } } - override suspend fun updatePoolsSbApy() { - polkaswapRepository.updatePoolsSbApy() - } +// override suspend fun updatePoolsSbApy() { +// polkaswapRepository.updatePoolsSbApy() +// } override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String) = polkaswapRepository.getPoolStrategicBonusAPY(reserveAccountOfPool) From d41503848357a20dbe5698bd433926e5d0e9a4bc Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 26 Jul 2024 10:43:11 +0500 Subject: [PATCH 34/84] remove liquidity --- common/src/main/res/values/strings.xml | 1 + .../data/DemeterFarmingRepository.kt | 7 + .../domain/DemeterFarmingPool.kt | 22 + .../interfaces/DemeterFarmingInteractor.kt | 8 + .../domain/interfaces/PoolsInteractor.kt | 14 +- .../navigation/InternalPoolsRouter.kt | 5 + .../navigation/LiquidityPoolsNavGraphRoute.kt | 20 +- .../impl/data/DemeterFarmingRepositoryImpl.kt | 310 ++++++++++ .../impl/data/DemeterStorage.kt | 37 ++ .../liquiditypools/impl/di/PoolsModule.kt | 36 +- .../domain/DemeterFarmingInteractorImpl.kt | 15 + .../impl/domain/PoolsInteractorImpl.kt | 12 + .../navigation/InternalPoolsRouterImpl.kt | 25 +- .../impl/presentation/PoolsFlowFragment.kt | 68 ++- .../impl/presentation/PoolsFlowViewModel.kt | 23 +- .../presentation/allpools/AllPoolsScreen.kt | 95 ++-- .../liquidityadd/LiquidityAddPresenter.kt | 10 +- .../liquidityadd/LiquidityAddScreen.kt | 30 +- .../LiquidityAddConfirmPresenter.kt | 29 +- .../LiquidityAddConfirmScreen.kt | 4 + .../LiquidityRemovePresenter.kt | 535 ++++++++++++++++++ .../liquidityremove/LiquidityRemoveScreen.kt | 187 ++++++ .../pooldetails/PoolDetailsPresenter.kt | 10 +- .../usecase/ValidateRemoveLiquidityUseCase.kt | 53 ++ .../jp/co/soramitsu/nft/impl/di/NFTModule.kt | 2 +- .../polkaswap/api/data/PolkaswapRepository.kt | 9 +- .../impl/data/PolkaswapRepositoryImpl.kt | 61 +- .../impl/data/network/blockchain/Extrinsic.kt | 21 + .../wallet/impl/presentation/WalletRouter.kt | 1 + 29 files changed, 1551 insertions(+), 99 deletions(-) create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f9b77235ee..c77a4bbe51 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -637,6 +637,7 @@ Your %s pooled Your pools + Your supply to Liquidity pools has been successfully completed All pools Reset to default Swap diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt new file mode 100644 index 0000000000..276f8a0c13 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt @@ -0,0 +1,7 @@ +package jp.co.soramitsu.liquiditypools.data + +import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool + +interface DemeterFarmingRepository { + suspend fun getFarmedPools(soraAccountAddress: String): List? +} \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt new file mode 100644 index 0000000000..7966e89aa0 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt @@ -0,0 +1,22 @@ +package jp.co.soramitsu.liquiditypools.domain + +import java.math.BigDecimal +import jp.co.soramitsu.wallet.impl.domain.model.Asset + +data class DemeterFarmingPool( + val tokenBase: Asset, + val tokenTarget: Asset, + val tokenReward: Asset, + val apr: Double, + val amount: BigDecimal, + val amountReward: BigDecimal, +) + +data class DemeterFarmingBasicPool( + val tokenBase: Asset, + val tokenTarget: Asset, + val tokenReward: Asset, + val apr: Double, + val tvl: BigDecimal, + val fee: Double, +) \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt new file mode 100644 index 0000000000..0f9d052b75 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt @@ -0,0 +1,8 @@ +package jp.co.soramitsu.liquiditypools.domain.interfaces + +import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId + +interface DemeterFarmingInteractor { + suspend fun getFarmedPools(chainId: ChainId): List? +} \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 7004c858c0..69ade44557 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -29,8 +29,8 @@ interface PoolsInteractor { suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, - tokenFrom: jp.co.soramitsu.core.models.Asset, - tokenTo: jp.co.soramitsu.core.models.Asset, + tokenFrom: Asset, + tokenTo: Asset, tokenFromAmount: BigDecimal, tokenToAmount: BigDecimal, pairEnabled: Boolean, @@ -38,6 +38,12 @@ interface PoolsInteractor { slippageTolerance: Double ): BigDecimal? + suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + ): BigDecimal? + suspend fun isPairEnabled( chainId: ChainId, inputTokenId: String, @@ -50,8 +56,8 @@ interface PoolsInteractor { suspend fun observeAddLiquidity( chainId: ChainId, - tokenFrom: jp.co.soramitsu.core.models.Asset, - tokenTo: jp.co.soramitsu.core.models.Asset, + tokenFrom: Asset, + tokenTo: Asset, amountFrom: BigDecimal, amountTo: BigDecimal, enabled: Boolean, diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index b0cf9c8cf4..85dd2619c3 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -9,6 +9,7 @@ interface InternalPoolsRouter { fun createNavGraphRoutesFlow(): Flow fun createNavGraphActionsFlow(): Flow fun back() + fun popupToScreen(route: LiquidityPoolsNavGraphRoute) fun openAllPoolsScreen(chainId: ChainId) fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) @@ -16,7 +17,11 @@ interface InternalPoolsRouter { fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) + fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) + fun openRemoveLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal) + fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) fun openErrorsScreen(title: String? = null, message: String) + fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) } \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 57c76a88ee..6f5f04758b 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -59,13 +59,23 @@ sealed interface LiquidityPoolsNavGraphRoute { } } - class LiquidityRemoveScreens( -// val token: NFT, - val receiver: String, - val isReceiverKnown: Boolean + class LiquidityRemoveScreen( + val chainId: ChainId, + val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { - override val routeName: String = "LiquidityAddScreens" + override val routeName: String = "LiquidityRemoveScreen" + } + } + + class LiquidityRemoveConfirmScreen( + val chainId: ChainId, + val ids: StringPair, + val amountFrom: BigDecimal, + val amountTo: BigDecimal + ) : LiquidityPoolsNavGraphRoute by Companion { + companion object : LiquidityPoolsNavGraphRoute { + override val routeName: String = "LiquidityRemoveConfirmScreen" } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt new file mode 100644 index 0000000000..1680429121 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt @@ -0,0 +1,310 @@ +package jp.co.soramitsu.liquiditypools.impl.data + +import java.math.BigDecimal +import java.math.BigInteger +import java.util.concurrent.ConcurrentHashMap +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.accountId +import jp.co.soramitsu.androidfoundation.format.addHexPrefix +import jp.co.soramitsu.androidfoundation.format.isZero +import jp.co.soramitsu.androidfoundation.format.mapBalance +import jp.co.soramitsu.androidfoundation.format.orZero +import jp.co.soramitsu.androidfoundation.format.safeCast +import jp.co.soramitsu.common.data.network.rpc.BulkRetriever +import jp.co.soramitsu.common.data.network.rpc.retrieveAllValues +import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository +import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingBasicPool +import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool +import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository +import jp.co.soramitsu.runtime.ext.addressOf +import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.shared_utils.extensions.toHexString +import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct +import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex +import jp.co.soramitsu.shared_utils.runtime.metadata.module +import jp.co.soramitsu.shared_utils.runtime.metadata.storage +import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey +import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId +import jp.co.soramitsu.shared_utils.wsrpc.executeAsync +import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository +import jp.co.soramitsu.wallet.impl.domain.model.Asset + +class DemeterFarmingRepositoryImpl( + private val chainRegistry: ChainRegistry, + private val bulkRetriever: BulkRetriever, + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository, + private val polkaswapRepository: PolkaswapRepository, +) : DemeterFarmingRepository { + + companion object { + private const val BLOCKS_PER_YEAR = 5256000 + } + + private val cachedFarmedPools = ConcurrentHashMap>() + private var cachedFarmedBasicPools: List? = null + + override suspend fun getFarmedPools( + chainId: String, + ): List? { + val soraAccountAddress = accountRepository.getSelectedAccount(chainId).address + + if (cachedFarmedPools.containsKey(soraAccountAddress)) return cachedFarmedPools[soraAccountAddress] + cachedFarmedPools.remove(soraAccountAddress) + + val baseFarms = getFarmedBasicPools(chainId) + val soraAssets = getSoraAssets(chainId) + + val calculated = getDemeter(chainId, soraAccountAddress) + ?.filter { it.farm && it.amount.isZero().not() } + ?.mapNotNull { demeter -> + val base = baseFarms.firstOrNull { base -> + base.tokenBase.token.configuration.currencyId == demeter.base && + base.tokenTarget.token.configuration.currencyId == demeter.pool && + base.tokenReward.token.configuration.currencyId == demeter.reward + } ?: return@mapNotNull null + + val baseTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == demeter.base + } ?: return@mapNotNull null + val poolTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == demeter.pool + } ?: return@mapNotNull null + val rewardTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == demeter.reward + } ?: return@mapNotNull null + + DemeterFarmingPool( + tokenBase = baseTokenMapped, + tokenTarget = poolTokenMapped, + tokenReward = rewardTokenMapped, + apr = base.apr, + amount = mapBalance(demeter.amount, baseTokenMapped.token.configuration.precision), + amountReward = mapBalance(demeter.rewardAmount, rewardTokenMapped.token.configuration.precision), + ) + } ?: return null + return cachedFarmedPools.getOrPut(soraAccountAddress) { calculated } + } + + suspend fun getFarmedBasicPools(chainId: ChainId): List { + if (cachedFarmedBasicPools == null) { + val rewardTokens = getRewardTokens(chainId) + + val soraAssets = getSoraAssets(chainId) + + cachedFarmedBasicPools = getAllFarms(chainId) + .mapNotNull { basic -> + runCatching { + val baseTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == basic.base + } ?: return@mapNotNull null + val poolTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == basic.pool + } ?: return@mapNotNull null + val rewardTokenMapped = soraAssets.firstOrNull { + it.token.configuration.currencyId == basic.reward + } ?: return@mapNotNull null + val rewardToken = rewardTokens.find { it.token == basic.reward } + val emission = getEmission(basic, rewardToken, rewardTokenMapped.token.configuration.precision) + val total = mapBalance(basic.totalTokensInPool, poolTokenMapped.token.configuration.precision) + val poolTokenPrice = poolTokenMapped.token.fiatRate.orZero() + val rewardTokenPrice = rewardTokenMapped.token.fiatRate.orZero() + val tvl = if (basic.isFarm) { + polkaswapRepository.getBasicPool(chainId, basic.base, basic.pool)?.let { pool -> + val kf = pool.targetReserves.div(pool.totalIssuance) + kf.times(total).times(2.toBigDecimal()).times(poolTokenPrice) + } ?: BigDecimal.ZERO + } else { + total.times(poolTokenPrice) + } + val apr = if (tvl.isZero()) BigDecimal.ZERO else emission + .times(BLOCKS_PER_YEAR.toBigDecimal()) + .times(rewardTokenPrice) + .div(tvl).times(100.toBigDecimal()) + + DemeterFarmingBasicPool( + tokenBase = baseTokenMapped, + tokenTarget = poolTokenMapped, + tokenReward = rewardTokenMapped, + apr = apr.toDouble(), + tvl = tvl, + fee = mapBalance(basic.depositFee, baseTokenMapped.token.configuration.precision).toDouble() + .times(100.0), + ) + }.getOrNull() + } + } + + return cachedFarmedBasicPools ?: emptyList() + } + + private suspend fun getSoraAssets(chainId: ChainId): List { + val soraChain = chainRegistry.getChain(chainId) + val wallet = accountRepository.getSelectedMetaAccount() + val accountId = wallet.accountId(soraChain) + val soraAssets = soraChain.assets.mapNotNull { chainAsset -> + accountId?.let { + walletRepository.getAsset( + metaId = wallet.id, + accountId = accountId, + chainAsset = chainAsset, + minSupportedVersion = null + ) + } + } + return soraAssets + } + + private suspend fun getRewardTokens(chainId: ChainId): List { + val runtime = chainRegistry.getRuntimeOrNull(chainId) ?: return emptyList() + val chain = chainRegistry.getChain(chainId) + + val storage = runtime.metadata.module("DemeterFarmingPlatform") + .storage("TokenInfos") + val type = storage.type.value ?: return emptyList() + val storageKey = storage.storageKey( + runtime + ) + + val socketService = chainRegistry.awaitConnection(chainId).socketService + + return bulkRetriever.retrieveAllValues(socketService, storageKey).mapNotNull { hex -> + hex.value?.let { hexValue -> + runCatching { + type.fromHex(runtime, hexValue) + ?.safeCast()?.let { decoded -> + decoded.get("teamAccount")?.let { chain.addressOf(it) } + DemeterRewardTokenStorage( + token = hex.key.assetIdFromKey(), + account = decoded.get("teamAccount")?.let { chain.addressOf(it) }.orEmpty(), + farmsTotalMultiplier = decoded.get("farmsTotalMultiplier")!!, + stakingTotalMultiplier = decoded.get("stakingTotalMultiplier")!!, + tokenPerBlock = decoded.get("tokenPerBlock")!!, + farmsAllocation = decoded.get("farmsAllocation")!!, + stakingAllocation = decoded.get("stakingAllocation")!!, + teamAllocation = decoded.get("teamAllocation")!!, + ) + } + }.getOrNull() + } + } + } + + private suspend fun getAllFarms(chainId: ChainId): List { + val runtime = chainRegistry.getRuntimeOrNull(chainId) ?: return emptyList() + val storage = runtime.metadata.module("DemeterFarmingPlatform") + .storage("Pools") + val type = storage.type.value ?: return emptyList() + val storageKey = storage.storageKey( + runtime, + ) + val socketService = chainRegistry.awaitConnection(chainId).socketService + val farms = bulkRetriever.retrieveAllValues(socketService, storageKey).mapNotNull { hex -> + hex.value?.let { hexValue -> + val decoded = type.fromHex(runtime, hexValue) + decoded?.safeCast>() + ?.filterIsInstance() + ?.mapNotNull { struct -> + runCatching { + DemeterBasicStorage( + base = struct.mapToToken("baseAsset")!!, + pool = hex.key.assetIdFromKey(1), + reward = hex.key.assetIdFromKey(), + multiplier = struct.get("multiplier")!!, + isCore = struct.get("isCore")!!, + isFarm = struct.get("isFarm")!!, + isRemoved = struct.get("isRemoved")!!, + depositFee = struct.get("depositFee")!!, + totalTokensInPool = struct.get("totalTokensInPool")!!, + rewards = struct.get("rewards")!!, + rewardsToBeDistributed = struct.get("rewardsToBeDistributed")!!, + ) + }.getOrNull() + } + } + }.flatten().filter { + it.isFarm && it.isRemoved.not() + } + return farms + } + + private fun getEmission( + basic: DemeterBasicStorage, + reward: DemeterRewardTokenStorage?, + precision: Int + ): BigDecimal { + val tokenMultiplier = + ((if (basic.isFarm) reward?.farmsTotalMultiplier else reward?.stakingTotalMultiplier))?.toBigDecimal( + precision + ) ?: BigDecimal.ZERO + if (tokenMultiplier.isZero()) return BigDecimal.ZERO + val multiplier = basic.multiplier.toBigDecimal(precision).div(tokenMultiplier) + val allocation = + mapBalance( + (if (basic.isFarm) reward?.farmsAllocation else reward?.stakingAllocation) + ?: BigInteger.ZERO, + precision + ) + val tokenPerBlock = reward?.tokenPerBlock?.toBigDecimal(precision) ?: BigDecimal.ZERO + return allocation.times(tokenPerBlock).times(multiplier) + } + + private suspend fun getDemeter(chainId: ChainId, address: String): List? { + val runtime = chainRegistry.getRuntimeOrNull(chainId) ?: return emptyList() + val storage = runtime.metadata.module("DemeterFarmingPlatform") + .storage("UserInfos") + val storageKey = storage.storageKey( + runtime, + address.toAccountId(), + ) + return getStorageHex(chainId, storageKey)?.let { hex -> + storage.type.value + ?.fromHex(runtime, hex) + ?.safeCast>() + ?.filterIsInstance() + ?.mapNotNull { instance -> + val baseToken = instance.mapToToken("baseAsset") + val poolToken = instance.mapToToken("poolAsset") + val rewardToken = instance.mapToToken("rewardAsset") + val isFarm = instance.get("isFarm") + val pooled = instance.get("pooledTokens") + val rewards = instance.get("rewards") + if (isFarm != null && baseToken != null && poolToken != null && + rewardToken != null && pooled != null && rewards != null + ) { + DemeterStorage( + base = baseToken, + pool = poolToken, + reward = rewardToken, + farm = isFarm, + amount = pooled, + rewardAmount = rewards, + ) + } else { + null + } + } + } + } + + private suspend fun getStorageHex(chainId: ChainId, storageKey: String): String? = + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo(), + ).result + + fun Struct.Instance.mapToToken(field: String) = + this.get(field)?.getTokenId()?.toHexString(true) + + fun Struct.Instance.getTokenId() = get>("code") + ?.map { (it as BigInteger).toByte() } + ?.toByteArray() + + fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() + fun String.assetIdFromKey(pos: Int): String = + this.substring(0, this.length - (64 * pos)).assetIdFromKey() + +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt new file mode 100644 index 0000000000..599b2bbfdf --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt @@ -0,0 +1,37 @@ +package jp.co.soramitsu.liquiditypools.impl.data + +import java.math.BigInteger + +class DemeterRewardTokenStorage( + val token: String, + val account: String, + val farmsTotalMultiplier: BigInteger, + val stakingTotalMultiplier: BigInteger, + val tokenPerBlock: BigInteger, + val farmsAllocation: BigInteger, + val stakingAllocation: BigInteger, + val teamAllocation: BigInteger, +) + +class DemeterBasicStorage( + val base: String, + val pool: String, + val reward: String, + val multiplier: BigInteger, + val isCore: Boolean, + val isFarm: Boolean, + val isRemoved: Boolean, + val depositFee: BigInteger, + val totalTokensInPool: BigInteger, + val rewards: BigInteger, + val rewardsToBeDistributed: BigInteger, +) + +class DemeterStorage( + val base: String, + val pool: String, + val reward: String, + val farm: Boolean, + val amount: BigInteger, + val rewardAmount: BigInteger, +) \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index eb859b5576..fc45e07a1c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -6,14 +6,20 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider +import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository +import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.data.DemeterFarmingRepositoryImpl +import jp.co.soramitsu.liquiditypools.impl.domain.DemeterFarmingInteractorImpl import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter @Module @@ -22,7 +28,7 @@ class PoolsModule { @Provides @Singleton - fun providesPoolIteractor( + fun providesPoolInteractor( polkaswapRepository: PolkaswapRepository, accountRepository: AccountRepository, polkaswapInteractor: PolkaswapInteractor, @@ -33,5 +39,31 @@ class PoolsModule { @Provides @Singleton - fun provideInnerLiquidityPoolsRouter(walletRouter: WalletRouter): InternalPoolsRouter = InternalPoolsRouterImpl() + fun provideInternalLiquidityPoolsRouter(walletRouter: WalletRouter): InternalPoolsRouter = InternalPoolsRouterImpl( + walletRouter = walletRouter + ) + + @Provides + @Singleton + fun provideDemeterFarmingInteractor( + demeterFarmingRepository: DemeterFarmingRepository, + ) : DemeterFarmingInteractor = + DemeterFarmingInteractorImpl(demeterFarmingRepository) + + @Provides + @Singleton + fun provideDemeterFarmingRepository( + chainRegistry: ChainRegistry, + bulkRetriever: BulkRetriever, + accountRepository: AccountRepository, + walletRepository: WalletRepository, + polkaswapRepository: PolkaswapRepository, + ) : DemeterFarmingRepository = + DemeterFarmingRepositoryImpl( + chainRegistry, + bulkRetriever, + accountRepository, + walletRepository, + polkaswapRepository + ) } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt new file mode 100644 index 0000000000..3b46047d67 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt @@ -0,0 +1,15 @@ +package jp.co.soramitsu.liquiditypools.impl.domain + +import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository +import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool +import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId + +class DemeterFarmingInteractorImpl( + private val demeterFarmingRepository: DemeterFarmingRepository, +) : DemeterFarmingInteractor { + + override suspend fun getFarmedPools(chainId: ChainId): List? { + return demeterFarmingRepository.getFarmedPools(chainId) + } +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 5dc5fb9a72..a6cf9af9cd 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -85,6 +85,18 @@ class PoolsInteractorImpl( ) } + override suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + ): BigDecimal? { + return polkaswapRepository.calcRemoveLiquidityNetworkFee( + chainId, + tokenFrom, + tokenTo + ) + } + // override suspend fun updateApy() { // polkaswapInteractor.updatePoolsSbApy() // } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 65b5ba7edc..db99d5e403 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -7,12 +7,15 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onEach -class InternalPoolsRouterImpl: InternalPoolsRouter { +class InternalPoolsRouterImpl( + private val walletRouter: WalletRouter +): InternalPoolsRouter { private val routesStack = Stack() private val mutableActionsFlow = @@ -30,6 +33,14 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableActionsFlow.tryEmit(NavAction.BackPressed) } + override fun popupToScreen(route: LiquidityPoolsNavGraphRoute) { + if (routesStack.any { it.routeName == route.routeName }) { + do { + val pop = routesStack.pop() + } while (pop.routeName != route.routeName) + } + } + override fun openAllPoolsScreen(chainId: ChainId) { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen(chainId)) } @@ -46,6 +57,14 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(chainId, ids, amountFrom, amountTo, apy)) } + override fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen(chainId, ids)) + } + + override fun openRemoveLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountFrom, amountTo)) + } + override fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen(chainId, isUserPools)) } @@ -54,4 +73,8 @@ class InternalPoolsRouterImpl: InternalPoolsRouter { mutableActionsFlow.tryEmit(NavAction.ShowError(title, message)) } + override fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) { + walletRouter.openOperationSuccess(txHash, chainId, customMessage) + } + } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 7606668e37..7d3e6391f2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -9,8 +9,12 @@ import android.widget.FrameLayout import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -35,16 +39,19 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment import jp.co.soramitsu.common.compose.component.BottomSheetDialog -import jp.co.soramitsu.common.compose.component.BottomSheetScreen +import jp.co.soramitsu.common.compose.component.Image import jp.co.soramitsu.common.compose.component.MainToolbarShimmer +import jp.co.soramitsu.common.compose.component.NavigationIconButton import jp.co.soramitsu.common.compose.component.ToolbarBottomSheet import jp.co.soramitsu.common.compose.component.ToolbarHomeIconState import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.models.retrieveString import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemoveScreen import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute @@ -139,15 +146,19 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment -> - ToolbarBottomSheet( - modifier = Modifier.padding(horizontal = 16.dp), - title = loadingState.data.retrieveString(), - onNavigationClick = remember { - { - viewModel.onNavigationClick() + if (loadingState.data.retrieveString().isEmpty()) { + PolkaswapImageToolbar() + } else { + ToolbarBottomSheet( + modifier = Modifier.padding(horizontal = 16.dp), + title = loadingState.data.retrieveString(), + onNavigationClick = remember { + { + viewModel.onNavigationClick() + } } - } - ) + ) + } is LoadingState.Loading -> MainToolbarShimmer( @@ -197,6 +208,11 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment) { behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.isHideable = true diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 23ba0e1ab8..11d9a40486 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -22,6 +22,9 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAd import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmCallbacks import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmPresenter import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmState +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemoveCallbacks +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemovePresenter +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemoveState import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsCallbacks import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsPresenter import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsState @@ -49,6 +52,7 @@ class PoolsFlowViewModel @Inject constructor( poolDetailsPresenter: PoolDetailsPresenter, liquidityAddPresenter: LiquidityAddPresenter, liquidityAddConfirmPresenter: LiquidityAddConfirmPresenter, + liquidityRemovePresenter: LiquidityRemovePresenter, private val coroutinesStore: CoroutinesStore, private val poolsInteractor: PoolsInteractor, private val accountInteractor: AccountInteractor, @@ -59,7 +63,8 @@ class PoolsFlowViewModel @Inject constructor( LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter, AllPoolsScreenInterface by allPoolsPresenter, PoolListScreenInterface by poolListPresenter, - PoolDetailsCallbacks by poolDetailsPresenter + PoolDetailsCallbacks by poolDetailsPresenter, + LiquidityRemoveCallbacks by liquidityRemovePresenter { val allPoolsScreenState: StateFlow = @@ -74,6 +79,9 @@ class PoolsFlowViewModel @Inject constructor( val liquidityAddScreenState: StateFlow = liquidityAddPresenter.createScreenStateFlow(coroutinesStore.uiScope) + val liquidityRemoveScreenState: StateFlow = + liquidityRemovePresenter.createScreenStateFlow(coroutinesStore.uiScope) + val liquidityAddConfirmState: StateFlow = liquidityAddConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) @@ -113,7 +121,7 @@ class PoolsFlowViewModel @Inject constructor( LiquidityPoolsNavGraphRoute.AllPoolsScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("All pools") + TextModel.SimpleString("") ) LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName -> { @@ -135,6 +143,16 @@ class PoolsFlowViewModel @Inject constructor( TextModel.SimpleString("Supply liquidity") ) + LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("Confirm liquidity") + ) + + LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("Remove liquidity") + ) + else -> LoadingState.Loading() } @@ -142,7 +160,6 @@ class PoolsFlowViewModel @Inject constructor( } override fun onPoolClicked(pair: StringPair) { -// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") internalPoolsRouter.openDetailsPoolScreen(polkaswapChainId, pair) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 39ccb121ed..a58036b3c2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -12,13 +12,16 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll 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.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -51,55 +54,63 @@ fun AllPoolsScreen( state: AllPoolsState, callback: AllPoolsScreenInterface ) { - Column( - modifier = Modifier.fillMaxSize(), + Box( + modifier = Modifier + .fillMaxSize() + .nestedScroll(rememberNestedScrollInteropConnection()), ) { - MarginVertical(margin = 16.dp) + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + MarginVertical(margin = 16.dp) - if (state.userPools.isNotEmpty()) { - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(0.dp), + if (state.userPools.isNotEmpty()) { + BackgroundCorneredWithBorder( modifier = Modifier - .wrapContentHeight() + .padding(horizontal = 16.dp) ) { - PoolGroupHeader( - title = stringResource(id = R.string.pl_your_pools), - onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } - ) - state.userPools.forEach { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier + .wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_your_pools), + onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } ) + state.userPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } + MarginVertical(margin = 16.dp) } - MarginVertical(margin = 16.dp) - } - if (state.allPools.isNotEmpty()) { - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.wrapContentHeight() + if (state.allPools.isNotEmpty()) { + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) ) { - PoolGroupHeader( - title = stringResource(id = R.string.pl_all_pools), - onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } - ) - state.allPools.forEach { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier.wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_all_pools), + onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) + state.allPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } } @@ -166,8 +177,8 @@ private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { @Preview @Composable -private fun PreviewAllPoolsInternal() { -val itemState = BasicPoolListItemState( +private fun PreviewAllPoolsScreen() { + val itemState = BasicPoolListItemState( ids = "0" to "1", token1Icon = "DEFAULT_ICON_URI", token2Icon = "DEFAULT_ICON_URI", diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 2ed0ba73c1..302c928abf 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -405,7 +405,7 @@ class LiquidityAddPresenter @Inject constructor( ) } - override fun onReviewClick() { + override fun onAddReviewClick() { setButtonLoading(true) println("!!! should setButtonLoading(true)") @@ -450,28 +450,28 @@ class LiquidityAddPresenter @Inject constructor( } } - override fun onFromAmountChange(amount: BigDecimal) { + override fun onAddFromAmountChange(amount: BigDecimal) { enteredFromAmountFlow.value = amount amountFrom = amount updateButtonState() } - override fun onToAmountChange(amount: BigDecimal) { + override fun onAddToAmountChange(amount: BigDecimal) { enteredToAmountFlow.value = amount amountTo = amount updateButtonState() } - override fun onFromAmountFocusChange(isFocused: Boolean) { + override fun onAddFromAmountFocusChange(isFocused: Boolean) { isFromAmountFocused.value = isFocused if (desired != WithDesired.INPUT) { desired = WithDesired.INPUT } } - override fun onToAmountFocusChange(isFocused: Boolean) { + override fun onAddToAmountFocusChange(isFocused: Boolean) { isToAmountFocused.value = isFocused if (desired != WithDesired.OUTPUT) { desired = WithDesired.OUTPUT diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index c7a21c358e..4f8bc1af14 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -50,15 +50,15 @@ data class LiquidityAddState( interface LiquidityAddCallbacks { - fun onReviewClick() + fun onAddReviewClick() - fun onFromAmountChange(amount: BigDecimal) + fun onAddFromAmountChange(amount: BigDecimal) - fun onFromAmountFocusChange(isFocused: Boolean) + fun onAddFromAmountFocusChange(isFocused: Boolean) - fun onToAmountChange(amount: BigDecimal) + fun onAddToAmountChange(amount: BigDecimal) - fun onToAmountFocusChange(isFocused: Boolean) + fun onAddToAmountFocusChange(isFocused: Boolean) } @Composable @@ -96,8 +96,8 @@ fun LiquidityAddScreen( AmountInput( state = state.fromAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onFromAmountChange, - onInputFocusChange = callbacks::onFromAmountFocusChange, + onInput = callbacks::onAddFromAmountChange, + onInputFocusChange = callbacks::onAddFromAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) @@ -106,8 +106,8 @@ fun LiquidityAddScreen( AmountInput( state = state.toAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onToAmountChange, - onInputFocusChange = callbacks::onToAmountFocusChange, + onInput = callbacks::onAddToAmountChange, + onInputFocusChange = callbacks::onAddToAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) } @@ -168,7 +168,7 @@ fun LiquidityAddScreen( text = "Review", enabled = state.buttonEnabled, loading = state.buttonLoading, - onClick = callbacks::onReviewClick + onClick = { runCallback(callbacks::onAddReviewClick) } ) MarginVertical(margin = 8.dp) @@ -187,11 +187,11 @@ private fun PreviewLiquidityAddScreen() { slippage = "0.5%" ), callbacks = object : LiquidityAddCallbacks { - override fun onReviewClick() {} - override fun onFromAmountChange(amount: BigDecimal) {} - override fun onFromAmountFocusChange(isFocused: Boolean) {} - override fun onToAmountChange(amount: BigDecimal) {} - override fun onToAmountFocusChange(isFocused: Boolean) {} + override fun onAddReviewClick() {} + override fun onAddFromAmountChange(amount: BigDecimal) {} + override fun onAddFromAmountFocusChange(isFocused: Boolean) {} + override fun onAddToAmountChange(amount: BigDecimal) {} + override fun onAddToAmountFocusChange(isFocused: Boolean) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 78cc726f50..040ee2e8d4 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -5,6 +5,7 @@ import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail @@ -12,6 +13,7 @@ import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter @@ -44,6 +46,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( private val chainsRepository: ChainsRepository, private val poolsInteractor: PoolsInteractor, private val accountInteractor: AccountInteractor, + private val resourceManager: ResourceManager, ) : LiquidityAddConfirmCallbacks { private val _stateSlippage = MutableStateFlow(0.5) @@ -121,7 +124,8 @@ class LiquidityAddConfirmPresenter @Inject constructor( feeInfoViewStateFlow.onEach { stateFlow.value = stateFlow.value.copy( - feeInfo = it + feeInfo = it, + buttonEnabled = it.feeAmount.isNullOrEmpty().not() ) }.launchIn(coroutineScope) @@ -195,7 +199,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( } override fun onConfirmClick() { - println("!!! LiquidityAddConfirm onConfirmClick") + setButtonLoading(true) coroutinesStore.ioScope.launch { val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch @@ -206,7 +210,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( val pairPresented = true var result = "" try { - println("!!! LiquidityAddConfirm onConfirmClick run observeAddLiquidity") result = poolsInteractor.observeAddLiquidity( chainId, tokenFrom, @@ -218,14 +221,28 @@ class LiquidityAddConfirmPresenter @Inject constructor( _stateSlippage.value, ) } catch (t: Throwable) { - internalPoolsRouter.openErrorsScreen(message = t.message.orEmpty()) + coroutinesStore.uiScope.launch { + internalPoolsRouter.openErrorsScreen(message = t.message.orEmpty()) + } } if (result.isNotEmpty()) { - println("!!! LiquidityAddConfirm onConfirmClick result = $result") - // TODO ALL DONE SCREEN + coroutinesStore.uiScope.launch { +// internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) + internalPoolsRouter.back() + internalPoolsRouter.back() + internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.pl_liquidity_add_complete)) + } } + }.invokeOnCompletion { + println("!!! add confirm invokeOnCompletion") + setButtonLoading(false) } } + private fun setButtonLoading(loading: Boolean) { + stateFlow.value = stateFlow.value.copy( + buttonLoading = loading + ) + } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index f261886f57..bee49245a1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -55,6 +55,8 @@ data class LiquidityAddConfirmState( val slippage: String = "0.5%", val apy: String? = null, val feeInfo: FeeInfoViewState = FeeInfoViewState.default, + val buttonEnabled: Boolean = false, + val buttonLoading: Boolean = false ) interface LiquidityAddConfirmCallbacks { @@ -202,6 +204,8 @@ fun LiquidityAddConfirmScreen( .padding(horizontal = 16.dp) .fillMaxWidth(), text = "Confirm", + enabled = state.buttonEnabled, + loading = state.buttonLoading, onClick = callbacks::onConfirmClick ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt new file mode 100644 index 0000000000..c3ad6d22bb --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -0,0 +1,535 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove + +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.androidfoundation.format.isZero +import jp.co.soramitsu.common.base.errors.ValidationException +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatCryptoDetail +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.common.utils.isNotZero +import jp.co.soramitsu.common.utils.moreThanZero +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.common.utils.requireValue +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateRemoveLiquidityUseCase +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData +import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.wallet.api.domain.fromValidationResult +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlin.math.min +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +@OptIn(FlowPreview::class) +class LiquidityRemovePresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val demeterFarmingInteractor: DemeterFarmingInteractor, + private val accountInteractor: AccountInteractor, + private val resourceManager: ResourceManager, + private val validateRemoveLiquidityUseCase: ValidateRemoveLiquidityUseCase, +) : LiquidityRemoveCallbacks { + private val enteredFromAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredToAmountFlow = MutableStateFlow(BigDecimal.ZERO) + + private var amountFrom: BigDecimal = BigDecimal.ZERO + private var amountTo: BigDecimal = BigDecimal.ZERO + + private val isFromAmountFocused = MutableStateFlow(false) + private val isToAmountFocused = MutableStateFlow(false) + + private var poolInFarming = false + private var poolDataUsable: CommonUserPoolData? = null + private var poolDataReal: CommonUserPoolData? = null + private var percent: Double = 0.0 + + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + @OptIn(ExperimentalCoroutinesApi::class) + val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> + val ids = screenArgs.ids + val chainId = screenArgs.chainId + println("!!! assetsInPoolFlow ids = $ids") + val assetsFlow = walletInteractor.assetsFlow().mapNotNull { + val firstInPair = it.firstOrNull { + it.asset.token.configuration.currencyId == ids.first + && it.asset.token.configuration.chainId == chainId + } + val secondInPair = it.firstOrNull { + it.asset.token.configuration.currencyId == ids.second + && it.asset.token.configuration.chainId == chainId + } + + println("!!! assetsInPoolFlow result% $firstInPair; $secondInPair") + if (firstInPair == null || secondInPair == null) { + return@mapNotNull null + } else { + firstInPair to secondInPair + } + } + assetsFlow + }.distinctUntilChanged() + + val tokensInPoolFlow = assetsInPoolFlow.map { + it.first.asset.token.configuration to it.second.asset.token.configuration + }.distinctUntilChanged() + + @OptIn(ExperimentalCoroutinesApi::class) + val poolDataFlow = screenArgsFlow.flatMapLatest { + val (tokenFromId, tokenToId) = it.ids + poolsInteractor.getPoolData(it.chainId, tokenFromId, tokenToId) + } + + init { + coroutinesStore.ioScope.launch { + poolDataFlow.map { data -> + println("!!! poolDataFlow data = $data") + data.user?.let { + CommonUserPoolData( + data.basic, + data.user!!, + ) + } + } + .catch { showError(it) } + .distinctUntilChanged() + .debounce(500) + .map { poolDataLocal -> + println("!!! poolDataFlow map poolDataLocal = $poolDataLocal") + poolDataReal = poolDataLocal + poolInFarming = false + + val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@map null + val (token1Id, token2Id) = ids + + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId + val result = if (poolDataLocal != null && chainId != null) { + val maxPercent = demeterFarmingInteractor.getFarmedPools(chainId)?.filter { pool -> + pool.tokenBase.token.configuration.currencyId == token1Id + && pool.tokenTarget.token.configuration.currencyId == token2Id + }?.maxOfOrNull { + PolkaswapFormulas.calculateShareOfPoolFromAmount( + it.amount, + poolDataLocal.user.poolProvidersBalance, + ) + } + + if (maxPercent != null && !maxPercent.isNaN()) { + poolInFarming = true + val usablePercent = 100 - maxPercent + poolDataLocal.copy( + user = poolDataLocal.user.copy( + basePooled = PolkaswapFormulas.calculateAmountByPercentage( + poolDataLocal.user.basePooled, + usablePercent, + poolDataLocal.basic.baseToken.token.configuration.precision, + ), + targetPooled = PolkaswapFormulas.calculateAmountByPercentage( + poolDataLocal.user.targetPooled, + usablePercent, + poolDataLocal.basic.baseToken.token.configuration.precision, + ), + poolProvidersBalance = PolkaswapFormulas.calculateAmountByPercentage( + poolDataLocal.user.poolProvidersBalance, + usablePercent, + poolDataLocal.basic.baseToken.token.configuration.precision, + ), + ) + ) + } else { + poolDataLocal + } + } else { + null + } + println("!!! poolDataFlow result = $result") + result + } + .collectLatest { poolDataLocal -> + println("!!! poolDataFlow collectLatest poolDataLocal = $poolDataLocal") + + poolDataUsable = poolDataLocal + amountFrom = + if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( + poolDataLocal.user.basePooled, + percent, + poolDataLocal.basic.baseToken.token.configuration.precision, + ) else BigDecimal.ZERO + amountTo = + if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( + poolDataLocal.user.targetPooled, + percent, + poolDataLocal.basic.targetToken?.token?.configuration?.precision!!, + ) else BigDecimal.ZERO + + coroutinesStore.ioScope.launch { + updateAmounts() + } + } + } + } + + @OptIn(FlowPreview::class) + private fun subscribeState(coroutineScope: CoroutineScope) { + poolDataFlow.onEach { + val baseToken = it.basic.baseToken.token + val targetToken = it.basic.targetToken?.token + + val pooledFromCrypto = it.user?.basePooled?.formatCrypto(baseToken.configuration.symbol).orEmpty() + val pooledFromFiat = it.user?.basePooled?.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol) + val argsFrom = pooledFromCrypto + pooledFromFiat?.let { " ($it)" } + val pooledFromBalance = resourceManager.getString(R.string.common_available_format, argsFrom) + + val pooledToCrypto = it.user?.targetPooled?.formatCrypto(targetToken?.configuration?.symbol).orEmpty() + val pooledToFiat = it.user?.targetPooled?.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol) + val argsTo = pooledToCrypto + pooledToFiat?.let { " ($it)" } + val pooledToBalance = resourceManager.getString(R.string.common_available_format, argsTo) + + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + tokenName = baseToken.configuration.symbol, + tokenImage = baseToken.configuration.iconUrl, + totalBalance = pooledFromBalance, + ), + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + tokenName = targetToken?.configuration?.symbol, + tokenImage = targetToken?.configuration?.iconUrl, + totalBalance = pooledToBalance, + ) + ) + }.launchIn(coroutineScope) + + utilityAssetFlow.onEach { + stateFlow.value = stateFlow.value.copy( + transferableAmount = it.transferable.formatCrypto(it.token.configuration.symbol), + transferableFiat = it.transferable.applyFiatRate(it.token.fiatRate)?.formatFiat(it.token.fiatSymbol) + ) + }.launchIn(coroutineScope) + + enteredFromAmountFlow.onEach { + val baseToken = assetsInPoolFlow.firstOrNull()?.first?.asset?.token + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + fiatAmount = it.applyFiatRate(baseToken?.fiatRate)?.formatFiat(baseToken?.fiatSymbol), + tokenAmount = it, + ) + ) + } + .debounce(900) + .onEach { amount -> + poolDataUsable?.let { + amountFrom = if (it.user.basePooled <= amount) amount else it.user.basePooled + + val precisionTo = poolDataFlow.firstOrNull()?.basic?.targetToken?.token?.configuration?.precision + amountTo = PolkaswapFormulas.calculateOneAmountFromAnother( + amountFrom, + it.user.basePooled, + it.user.targetPooled, + precisionTo + ) + percent = PolkaswapFormulas.calculateShareOfPoolFromAmount( + amountFrom, + it.user.basePooled, + ) + } + + coroutinesStore.uiScope.launch { + updateAmounts() + } + }.launchIn(coroutineScope) + + enteredToAmountFlow.onEach { + println("!!! enteredToAmountFlow.onEach = $it") + + val targetToken = assetsInPoolFlow.firstOrNull()?.second?.asset?.token + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + fiatAmount = it.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol), + tokenAmount = it + ), + ) + } + .debounce(900) + .onEach { amount -> + println("!!! enteredToAmountFlow.onEach debounced = $amount") + poolDataUsable?.let { + amountTo = if (amount <= it.user.targetPooled) amount else it.user.targetPooled + + val precisionFrom = poolDataFlow.firstOrNull()?.basic?.baseToken?.token?.configuration?.precision + amountFrom = PolkaswapFormulas.calculateOneAmountFromAnother( + amountTo, + it.user.targetPooled, + it.user.basePooled, + precisionFrom + ) + percent = PolkaswapFormulas.calculateShareOfPoolFromAmount( + amountFrom, + it.user.basePooled, + ) + } + +// updateAmounts() + coroutinesStore.uiScope.launch { + updateAmounts() + } + }.launchIn(coroutineScope) + + isFromAmountFocused.onEach { + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + isFocused = it + ), + ) + }.launchIn(coroutineScope) + + isToAmountFocused.onEach { + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + isFocused = it + ), + ) + }.launchIn(coroutineScope) + + feeInfoViewStateFlow.onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it + ) + updateButtonState() + }.launchIn(coroutineScope) + } + + private val stateFlow = MutableStateFlow(LiquidityRemoveState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private suspend fun updateAmounts() { + assetsInPoolFlow.firstOrNull()?.let { (assetFrom, assetTo) -> + if (amountFrom.compareTo(stateFlow.value.fromAmountInputViewState.tokenAmount) != 0) { + println("!!! updateAmounts amountFrom to $amountFrom") + + val scaledAmountFrom = when { + amountFrom.isZero() -> BigDecimal.ZERO + else -> amountFrom.setScale( + min(MAX_DECIMALS_8, amountFrom.scale()), + RoundingMode.DOWN + ) + } + + stateFlow.value = stateFlow.value.copy( + fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + tokenAmount = scaledAmountFrom, + fiatAmount = amountFrom.applyFiatRate(assetFrom.asset.token.fiatRate)?.formatFiat(assetFrom.asset.token.fiatSymbol), + ) + ) + } + if (amountTo.compareTo(stateFlow.value.toAmountInputViewState.tokenAmount) != 0) { + println("!!! updateAmounts amountTo to $amountTo") + val scaledAmountTo = when { + amountTo.isZero() -> BigDecimal.ZERO + else -> amountTo.setScale( + min(MAX_DECIMALS_8, amountTo.scale()), + RoundingMode.DOWN + ) + } + stateFlow.value = stateFlow.value.copy( + toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + tokenAmount = scaledAmountTo, + fiatAmount = amountTo.applyFiatRate(assetTo.asset.token.fiatRate)?.formatFiat(assetTo.asset.token.fiatSymbol), + ) + ) + } + } + + updateButtonState() + } + + private fun updateButtonState() { + val isButtonEnabled = amountTo.moreThanZero() && amountTo.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null + stateFlow.value = stateFlow.value.copy( + buttonEnabled = isButtonEnabled + ) + } + + private val networkFeeFlow = tokensInPoolFlow.map { (baseAsset, targetAsset) -> + val networkFee = getRemoveLiquidityNetworkFee( + tokenFrom = baseAsset, + tokenTo = targetAsset, + ) + println("!!!! RemoveLiquidity FeeFlow emit $networkFee") + networkFee + } + + @OptIn(ExperimentalCoroutinesApi::class) + val utilityAssetFlow = screenArgsFlow.map { screenArgs -> + val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) + screenArgs.chainId to utilityAssetId + }.flatMapLatest { (chainId, utilityAssetId) -> + walletInteractor.assetFlow(chainId, utilityAssetId) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = + screenArgsFlow.map { screenArgs -> + val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) + screenArgs.chainId to utilityAssetId + }.flatMapLatest { (chainId, utilityAssetId) -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(chainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + + private suspend fun getRemoveLiquidityNetworkFee( + tokenFrom: Asset, + tokenTo: Asset, + ): BigDecimal { + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + + val result = poolsInteractor.calcRemoveLiquidityNetworkFee( + chainId, + tokenFrom, + tokenTo, + ) + return result ?: BigDecimal.ZERO + } + + private fun setButtonLoading(loading: Boolean) { + stateFlow.value = stateFlow.value.copy( + buttonLoading = loading + ) + } + + override fun onRemoveReviewClick() { + setButtonLoading(true) + + coroutinesStore.uiScope.launch { + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + + val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) + val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total + val feeAmount = networkFeeFlow.firstOrNull().orZero() + + val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch + + val validationResult = validateRemoveLiquidityUseCase( + assetFrom = poolAssets.first, + assetTo = poolAssets.second, + utilityAssetId = utilityAssetId, + utilityAmount = utilityAmount.orZero(), + amountFrom = amountFrom, + amountTo = amountTo, + feeAmount = feeAmount, + ) + + validationResult.exceptionOrNull()?.let { + showError(it) + return@launch + } + + val validationValue = validationResult.requireValue() + ValidationException.fromValidationResult(validationValue, resourceManager)?.let { + showError(it) + return@launch + } + + val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch + internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo) + }.invokeOnCompletion { + println("!!! setButtonLoading(false)") + coroutinesStore.uiScope.launch { + delay(300) + setButtonLoading(false) + } + } + } + + override fun onRemoveFromAmountChange(amount: BigDecimal) { + println("!!! onRemoveFromAmountChange(amount = $amount") + enteredFromAmountFlow.value = amount +// amountFrom = amount + + updateButtonState() + } + + override fun onRemoveToAmountChange(amount: BigDecimal) { + println("!!! onRemoveToAmountChange(amount = $amount") + enteredToAmountFlow.value = amount +// amountTo = amount + + updateButtonState() + } + + override fun onRemoveFromAmountFocusChange(isFocused: Boolean) { + isFromAmountFocused.value = isFocused + } + + override fun onRemoveToAmountFocusChange(isFocused: Boolean) { + isToAmountFocused.value = isFocused + } + + private fun showError(throwable: Throwable) { + when (throwable) { + is ValidationException -> { + val (title, text) = throwable + internalPoolsRouter.openErrorsScreen(title, text) + } + + else -> { + throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } + } + } + } +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt new file mode 100644 index 0000000000..e022666491 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -0,0 +1,187 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import java.math.BigDecimal +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.AmountInput +import jp.co.soramitsu.common.compose.component.AmountInputViewState +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.compose.component.InfoTableItem +import jp.co.soramitsu.common.compose.component.InfoTableItemAsset +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.TitleIconValueState +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.theme.backgroundBlack +import jp.co.soramitsu.common.compose.theme.colorAccentDark +import jp.co.soramitsu.common.compose.theme.grayButtonBackground +import jp.co.soramitsu.common.compose.theme.white +import jp.co.soramitsu.common.compose.theme.white08 +import jp.co.soramitsu.feature_wallet_impl.R + +data class LiquidityRemoveState( + val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val toAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val transferableAmount: String? = null, + val transferableFiat: String? = null, + val feeInfo: FeeInfoViewState = FeeInfoViewState.default, + val buttonEnabled: Boolean = false, + val buttonLoading: Boolean = false +) + +interface LiquidityRemoveCallbacks { + + fun onRemoveReviewClick() + + fun onRemoveFromAmountChange(amount: BigDecimal) + + fun onRemoveFromAmountFocusChange(isFocused: Boolean) + + fun onRemoveToAmountChange(amount: BigDecimal) + + fun onRemoveToAmountFocusChange(isFocused: Boolean) +} + +@Composable +fun LiquidityRemoveScreen( + state: LiquidityRemoveState, + callbacks: LiquidityRemoveCallbacks +) { + val keyboardController = LocalSoftwareKeyboardController.current + val runCallback: (() -> Unit) -> Unit = { block -> + keyboardController?.hide() + block() + } + + Column( + modifier = Modifier + .background(backgroundBlack) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MarginVertical(margin = 16.dp) + + Box( + modifier = Modifier + .padding(top = 8.dp), + contentAlignment = Alignment.Center + ) { + Column { + AmountInput( + state = state.fromAmountInputViewState, + borderColorFocused = colorAccentDark, + onInput = callbacks::onRemoveFromAmountChange, + onInputFocusChange = callbacks::onRemoveFromAmountFocusChange, + onKeyboardDone = { keyboardController?.hide() } + ) + + MarginVertical(margin = 8.dp) + + AmountInput( + state = state.toAmountInputViewState, + borderColorFocused = colorAccentDark, + onInput = callbacks::onRemoveToAmountChange, + onInputFocusChange = callbacks::onRemoveToAmountFocusChange, + onKeyboardDone = { keyboardController?.hide() } + ) + } + + Icon( + modifier = Modifier + .clip(CircleShape) + .background(grayButtonBackground) + .border(width = 1.dp, color = white08, shape = CircleShape) + .padding(8.dp), + painter = painterResource(R.drawable.ic_plus_white_24), + contentDescription = null, + tint = white + ) + } + + MarginVertical(margin = 24.dp) + + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + ) { + Column { + InfoTableItem( + TitleValueViewState( + title = "Transferable Balance", + value = state.transferableAmount, + additionalValue = state.transferableFiat + ) + ) + InfoTableItem( + TitleValueViewState( + title = "Network fee", + value = state.feeInfo.feeAmount, + additionalValue = state.feeInfo.feeAmountFiat, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) + ) + ) + } + } + + MarginVertical(margin = 24.dp) + } + + AccentButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Review", + enabled = state.buttonEnabled, + loading = state.buttonLoading, + onClick = { runCallback(callbacks::onRemoveReviewClick) } + ) + + MarginVertical(margin = 8.dp) + } +} + +@Preview +@Composable +private fun PreviewLiquidityRemoveScreen() { + LiquidityRemoveScreen( + state = LiquidityRemoveState( + fromAmountInputViewState = AmountInputViewState.defaultObj, + toAmountInputViewState = AmountInputViewState.defaultObj, + feeInfo = FeeInfoViewState.default, + ), + callbacks = object : LiquidityRemoveCallbacks { + override fun onRemoveReviewClick() {} + override fun onRemoveFromAmountChange(amount: BigDecimal) {} + override fun onRemoveFromAmountFocusChange(isFocused: Boolean) {} + override fun onRemoveToAmountChange(amount: BigDecimal) {} + override fun onRemoveToAmountFocusChange(isFocused: Boolean) {} + }, + ) +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index 9e218e3db9..6e0a162eda 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -75,14 +75,16 @@ class PoolDetailsPresenter @Inject constructor( } override fun onRemoveLiquidityClick() { - println("!!! onRemoveLiquidityClick") + coroutinesStore.ioScope.launch { + val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + internalPoolsRouter.openRemoveLiquidityScreen(chainId, ids) + } + } suspend fun observePoolDetails(chainId: ChainId, ids: StringPair): Flow { val (tokenFromId, tokenToId) = ids -// val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() -// val address = accountRepository.getSelectedAccount(payload.chainId).address - return poolsInteractor.getPoolData(chainId, tokenFromId, tokenToId) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt new file mode 100644 index 0000000000..64b3a0df3b --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt @@ -0,0 +1,53 @@ +package jp.co.soramitsu.liquiditypools.impl.usecase + +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.common.utils.orZero +import jp.co.soramitsu.wallet.api.domain.TransferValidationResult +import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus + +class ValidateRemoveLiquidityUseCase @Inject constructor() { + + operator fun invoke( + assetFrom: AssetWithStatus, + assetTo: AssetWithStatus, + utilityAssetId: String, + utilityAmount: BigDecimal, + amountFrom: BigDecimal, + amountTo: BigDecimal, + feeAmount: BigDecimal + ): Result { + return runCatching { + val isEnoughAmountFrom = amountFrom + feeAmount.takeIf { + assetFrom.asset.token.configuration.id == utilityAssetId + }.orZero() < assetFrom.asset.total.orZero() + + val isEnoughAmountTo = amountTo + feeAmount.takeIf { + assetTo.asset.token.configuration.id == utilityAssetId + }.orZero() < assetTo.asset.total.orZero() + + val isEnoughAmountFee = if (utilityAssetId in listOf(assetFrom.asset.token.configuration.id, assetTo.asset.token.configuration.id)) { + true + } else { + feeAmount < utilityAmount + } + + val validationChecks = mapOf ( + TransferValidationResult.InsufficientBalance to (!isEnoughAmountFrom || !isEnoughAmountTo), + TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee + ) + + val result = performChecks(validationChecks) + return Result.success(result) + } + } + + private fun performChecks( + checks: Map, + ): TransferValidationResult { + checks.forEach { (result, condition) -> + if (condition) return result + } + return TransferValidationResult.Valid + } +} \ No newline at end of file diff --git a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt index 93f6054c32..43177a38b7 100644 --- a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt +++ b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt @@ -5,6 +5,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.nft.data.NFTRepository @@ -37,7 +38,6 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index 3218c9295c..fc8c001772 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -61,6 +61,8 @@ interface PolkaswapRepository { suspend fun getBasicPools(chainId: ChainId): List + suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? + // suspend fun getPoolOfAccount( // address: String?, // tokenFromId: String, @@ -87,6 +89,12 @@ interface PolkaswapRepository { slippageTolerance: Double ): BigDecimal? + suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + ): BigDecimal? + suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int // suspend fun updatePoolsSbApy() fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? @@ -110,5 +118,4 @@ interface PolkaswapRepository { fun subscribePools(address: String): Flow> fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow - } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 1fa2c423c6..918a622566 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -42,6 +42,7 @@ import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.liquidityAdd import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.register +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.removeLiquidity import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.swap import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.ext.addressOf @@ -76,6 +77,7 @@ import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStora import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks +import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -351,7 +353,7 @@ class PolkaswapRepositoryImpl @Inject constructor( override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { val tempApy = blockExplorerManager.getTempApy(reserveAccountOfPool) - println("!!! blockExplorerManager getPoolStrategicBonusAPY for address $reserveAccountOfPool = $tempApy") +// println("!!! blockExplorerManager getPoolStrategicBonusAPY for address $reserveAccountOfPool = $tempApy") return tempApy } @@ -359,6 +361,40 @@ class PolkaswapRepositoryImpl @Inject constructor( // println("!!! call blockExplorerManager.updatePoolsSbApy()") // blockExplorerManager.updatePoolsSbApy() // } +override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? { + val poolLocal = poolDao.getBasicPool(baseTokenId, targetTokenId) ?: return null + + val soraChain = chainRegistry.getChain(chainId) + val wallet = accountRepository.getSelectedMetaAccount() + val accountId = wallet.accountId(soraChain) + val soraAssets = soraChain.assets.mapNotNull { chainAsset -> + accountId?.let { + walletRepository.getAsset( + metaId = wallet.id, + accountId = accountId, + chainAsset = chainAsset, + minSupportedVersion = null + ) + } + } + + val baseAsset = soraAssets.firstOrNull { + it.token.configuration.currencyId == baseTokenId + } ?: return null + val targetAsset = soraAssets.firstOrNull { + it.token.configuration.currencyId == targetTokenId + } + + return BasicPoolData( + baseToken = baseAsset, + targetToken = targetAsset, + baseReserves = poolLocal.reserveBase, + targetReserves = poolLocal.reserveTarget, + totalIssuance = poolLocal.totalIssuance, + reserveAccount = poolLocal.reservesAccount, + sbapy = getPoolStrategicBonusAPY(poolLocal.reservesAccount) + ) +} override suspend fun getBasicPools(chainId: ChainId): List { println("!!! getBasicPools() start") @@ -521,6 +557,29 @@ class PolkaswapRepositoryImpl @Inject constructor( return feeToken?.amountFromPlanks(fee) } + override suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + ): BigDecimal? { + val chain = chainRegistry.getChain(chainId) + val baseTokenId = tokenFrom.currencyId ?: return null + val targetTokenId = tokenTo.currencyId ?: return null + + val fee = extrinsicService.estimateFee(chain) { + removeLiquidity( + dexId = getPoolBaseTokenDexId(chainId, baseTokenId), + outputAssetIdA = baseTokenId, + outputAssetIdB = targetTokenId, + markerAssetDesired = tokenFrom.planksFromAmount(BigDecimal.ONE), + outputAMin = tokenFrom.planksFromAmount(BigDecimal.ONE), + outputBMin = tokenTo.planksFromAmount(BigDecimal.ONE), + ) + } + val feeToken = chain.utilityAsset + return feeToken?.amountFromPlanks(fee) + } + override suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int { return getPoolBaseTokens(chainId).first { it.second == tokenId diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt index a97deb8208..ba4bcf3c73 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt @@ -101,6 +101,27 @@ fun ExtrinsicBuilder.depositLiquidity( ) ) +fun ExtrinsicBuilder.removeLiquidity( + dexId: Int, + outputAssetIdA: String, + outputAssetIdB: String, + markerAssetDesired: BigInteger, + outputAMin: BigInteger, + outputBMin: BigInteger +) = + this.call( + Modules.POOL_XYK, + "withdraw_liquidity", + mapOf( + "dex_id" to dexId.toBigInteger(), + "output_asset_a" to outputAssetIdA.mapCodeToken(), + "output_asset_b" to outputAssetIdB.mapCodeToken(), + "marker_asset_desired" to markerAssetDesired, + "output_a_min" to outputAMin, + "output_b_min" to outputBMin + ) + ) + fun ExtrinsicBuilder.liquidityAdd( dexId: Int, tokenFrom: Asset, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt index 833ac786c3..9a400e3339 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt @@ -81,6 +81,7 @@ interface WalletRouter : SecureRouter, WalletRouterApi { fun openFilter() fun openOperationSuccess(operationHash: String?, chainId: ChainId?) + fun openOperationSuccess(operationHash: String?, chainId: ChainId?, customMessage: String?) fun openSendConfirm(transferDraft: TransferDraft, phishingType: PhishingType?, overrides: Map = emptyMap(), transferComment: String? = null, skipEdValidation: Boolean = false) From d07967f5b04a0da4f8cf3bab46dad542c5ac9867 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 29 Jul 2024 12:28:44 +0500 Subject: [PATCH 35/84] remove liquidity --- .../domain/interfaces/PoolsInteractor.kt | 10 + .../navigation/InternalPoolsRouter.kt | 10 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 5 +- .../impl/domain/PoolsInteractorImpl.kt | 29 ++- .../navigation/InternalPoolsRouterImpl.kt | 12 +- .../impl/presentation/PoolsFlowFragment.kt | 6 + .../impl/presentation/PoolsFlowViewModel.kt | 17 +- .../LiquidityRemovePresenter.kt | 39 ++- .../LiquidityRemoveConfirmPresenter.kt | 244 ++++++++++++++++++ .../LiquidityRemoveConfirmScreen.kt | 180 +++++++++++++ .../usecase/ValidateRemoveLiquidityUseCase.kt | 23 +- .../polkaswap/api/data/PolkaswapRepository.kt | 9 + .../impl/data/PolkaswapRepositoryImpl.kt | 30 +++ 13 files changed, 583 insertions(+), 31 deletions(-) create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 69ade44557..97c1b03e8f 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -66,4 +66,14 @@ interface PoolsInteractor { ): String suspend fun updatePools(chainId: ChainId) + + suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + networkFee: BigDecimal + ): String } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 85dd2619c3..961d10c7f4 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -18,7 +18,15 @@ interface InternalPoolsRouter { fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) - fun openRemoveLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal) + fun openRemoveLiquidityConfirmScreen( + chainId: ChainId, + ids: StringPair, + amountFrom: BigDecimal, + amountTo: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + desired: BigDecimal + ) fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 6f5f04758b..b11eb6d4ed 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -72,7 +72,10 @@ sealed interface LiquidityPoolsNavGraphRoute { val chainId: ChainId, val ids: StringPair, val amountFrom: BigDecimal, - val amountTo: BigDecimal + val amountTo: BigDecimal, + val firstAmountMin: BigDecimal, + val secondAmountMin: BigDecimal, + val desired: BigDecimal, ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "LiquidityRemoveConfirmScreen" diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index a6cf9af9cd..8951f99e79 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -2,7 +2,6 @@ package jp.co.soramitsu.liquiditypools.impl.domain import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository -import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.data.secrets.v1.Keypair import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets @@ -114,6 +113,31 @@ class PoolsInteractorImpl( ) } +// override suspend fun removeLiquidity( + override suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + networkFee: BigDecimal + ): String { + val address = accountRepository.getSelectedAccount(chainId).address + + val status = polkaswapRepository.observeRemoveLiquidity( + chainId, + tokenFrom, + tokenTo, + markerAssetDesired, + firstAmountMin, + secondAmountMin, + ) + + + return status?.getOrNull() ?: "" + } + override suspend fun observeAddLiquidity( chainId: ChainId, tokenFrom: Asset, @@ -124,9 +148,8 @@ class PoolsInteractorImpl( presented: Boolean, slippageTolerance: Double ): String { - val soraChain = chainRegistry.getChain(chainId) val metaAccount = accountRepository.getSelectedMetaAccount() - val address = metaAccount.address(soraChain) ?: return "" + val address = accountRepository.getSelectedAccount(chainId).address val networkFee = calcAddLiquidityNetworkFee( chainId, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index db99d5e403..6169c83d4f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -61,8 +61,16 @@ class InternalPoolsRouterImpl( mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen(chainId, ids)) } - override fun openRemoveLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountFrom, amountTo)) + override fun openRemoveLiquidityConfirmScreen( + chainId: ChainId, + ids: StringPair, + amountFrom: BigDecimal, + amountTo: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + desired: BigDecimal + ) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountFrom, amountTo, firstAmountMin, secondAmountMin, desired)) } override fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 7d3e6391f2..6aad843ecd 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -52,6 +52,7 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemoveScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm.LiquidityRemoveConfirmScreen import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsScreen import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute @@ -213,6 +214,11 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment = @@ -85,6 +90,9 @@ class PoolsFlowViewModel @Inject constructor( val liquidityAddConfirmState: StateFlow = liquidityAddConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) + val liquidityRemoveConfirmState: StateFlow = + liquidityRemoveConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) + val navGraphRoutesFlow: StateFlow = internalPoolsRouter.createNavGraphRoutesFlow().stateIn( @@ -152,7 +160,12 @@ class PoolsFlowViewModel @Inject constructor( LoadingState.Loaded( TextModel.SimpleString("Remove liquidity") ) - + + LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen.routeName -> + LoadingState.Loaded( + TextModel.SimpleString("Remove liquidity") + ) + else -> LoadingState.Loading() } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index c3ad6d22bb..12f15ca8e6 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -458,17 +458,20 @@ class LiquidityRemovePresenter @Inject constructor( coroutinesStore.uiScope.launch { val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) - val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total +// val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) +// val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total + val utilityAmount = utilityAssetFlow.firstOrNull()?.transferable ?: return@launch val feeAmount = networkFeeFlow.firstOrNull().orZero() val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch + val userBasePooled = poolDataReal?.user?.basePooled ?: return@launch + val userTargetPooled = poolDataReal?.user?.targetPooled ?: return@launch + val validationResult = validateRemoveLiquidityUseCase( - assetFrom = poolAssets.first, - assetTo = poolAssets.second, - utilityAssetId = utilityAssetId, utilityAmount = utilityAmount.orZero(), + userBasePooled = userBasePooled, + userTargetPooled = userTargetPooled, amountFrom = amountFrom, amountTo = amountTo, feeAmount = feeAmount, @@ -485,8 +488,32 @@ class LiquidityRemovePresenter @Inject constructor( return@launch } + val slippage = 0.5 + + val firstAmountMin = + PolkaswapFormulas.calculateMinAmount( + amountFrom, + slippage + ) + val secondAmountMin = + PolkaswapFormulas.calculateMinAmount( + amountTo, + slippage + ) + val desired = + if (percent == 100.0) { + poolDataUsable?.user?.poolProvidersBalance.orZero() + } else { + PolkaswapFormulas.calculateAmountByPercentage( + poolDataUsable?.user?.poolProvidersBalance.orZero(), + percent, + poolAssets.first.asset.token.configuration.precision + ) + } + val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo) + + internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo, firstAmountMin, secondAmountMin, desired) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt new file mode 100644 index 0000000000..0cbe3f6f95 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -0,0 +1,244 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm + +import java.math.BigDecimal +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import jp.co.soramitsu.account.api.domain.model.address +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.formatCrypto +import jp.co.soramitsu.common.utils.formatCryptoDetail +import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter +import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +class LiquidityRemoveConfirmPresenter @Inject constructor( + private val coroutinesStore: CoroutinesStore, + private val internalPoolsRouter: InternalPoolsRouter, + private val walletInteractor: WalletInteractor, + private val chainsRepository: ChainsRepository, + private val poolsInteractor: PoolsInteractor, + private val accountInteractor: AccountInteractor, + private val resourceManager: ResourceManager, +) : LiquidityRemoveConfirmCallbacks { + + private val _stateSlippage = MutableStateFlow(0.5) + val stateSlippage = _stateSlippage.asStateFlow() + + + private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() + .filterIsInstance() + .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + + val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> + val ids = screenArgs.ids + val chainId = screenArgs.chainId + val assetsFlow = walletInteractor.assetsFlow().mapNotNull { + val firstInPair = it.firstOrNull { + it.asset.token.configuration.currencyId == ids.first + && it.asset.token.configuration.chainId == chainId + } + val secondInPair = it.firstOrNull { + it.asset.token.configuration.currencyId == ids.second + && it.asset.token.configuration.chainId == chainId + } + if (firstInPair == null || secondInPair == null) { + return@mapNotNull null + } else { + firstInPair to secondInPair + } + } + assetsFlow + } + + val tokensInPoolFlow = assetsInPoolFlow.map { + it.first.asset.token to it.second.asset.token + }.distinctUntilChanged() + + val isPoolPairEnabled = + screenArgsFlow.map { screenArgs -> + val (tokenFromId, tokenToId) = screenArgs.ids + poolsInteractor.isPairEnabled( + screenArgs.chainId, + tokenFromId, + tokenToId + ) + } + + + init { + + } + private val stateFlow = MutableStateFlow(LiquidityRemoveConfirmState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetFrom, assetTo) -> + stateFlow.value = stateFlow.value.copy( + assetFromIconUrl = assetFrom.configuration.iconUrl, + assetToIconUrl = assetTo.configuration.iconUrl, + baseAmount = screenArgs.amountFrom.formatCrypto(assetFrom.configuration.symbol), + baseFiat = screenArgs.amountFrom.applyFiatRate(assetFrom.fiatRate)?.formatFiat(assetFrom.fiatSymbol).orEmpty(), + targetAmount = screenArgs.amountTo.formatCrypto(assetTo.configuration.symbol), + targetFiat = screenArgs.amountTo.applyFiatRate(assetTo.fiatRate)?.formatFiat(assetTo.fiatSymbol).orEmpty(), +// apy = screenArgs.apy + ) + }.launchIn(coroutineScope) + + feeInfoViewStateFlow.onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it, + buttonEnabled = it.feeAmount.isNullOrEmpty().not() + ) + }.launchIn(coroutineScope) + + } + + val networkFeeFlow = combine( + screenArgsFlow, + tokensInPoolFlow, + stateSlippage, + isPoolPairEnabled + ) + { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> + val networkFee = getLiquidityNetworkFee( + tokenFrom = baseAsset.configuration, + tokenTo = targetAsset.configuration, + tokenFromAmount = screenArgs.amountFrom, + tokenToAmount = screenArgs.amountTo, + pairEnabled = pairEnabled, + pairPresented = true, //pairPresented, + slippageTolerance = slippage + ) + println("!!!! networkFeeFlow emit $networkFee") + networkFee + } + + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = + screenArgsFlow.map { screenArgs -> + val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) + screenArgs.chainId to utilityAssetId + }.flatMapLatest { (chainId, utilityAssetId) -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(chainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + + private suspend fun getLiquidityNetworkFee( + tokenFrom: Asset, + tokenTo: Asset, + tokenFromAmount: BigDecimal, + tokenToAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal { + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + val soraChain = walletInteractor.getChain(chainId) + val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() + val result = poolsInteractor.calcAddLiquidityNetworkFee( + chainId, + user, + tokenFrom, + tokenTo, + tokenFromAmount, + tokenToAmount, + pairEnabled, + pairPresented, + slippageTolerance, + ) + return result ?: BigDecimal.ZERO + } + + override fun onRemoveConfirmClick() { + setButtonLoading(true) + coroutinesStore.ioScope.launch { + val firstAmountMin = screenArgsFlow.replayCache.firstOrNull()?.firstAmountMin ?: return@launch + val secondAmountMin = screenArgsFlow.replayCache.firstOrNull()?.secondAmountMin ?: return@launch + val desired = screenArgsFlow.replayCache.firstOrNull()?.desired ?: return@launch + val networkFee = networkFeeFlow.firstOrNull() ?: return@launch + + val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch + val tokenTo = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch + + var result = "" + + + try { + result = poolsInteractor.observeRemoveLiquidity( + chainId, + tokenFrom, + tokenTo, + desired, + firstAmountMin, + secondAmountMin, + networkFee + ) + } catch (t: Throwable) { + coroutinesStore.uiScope.launch { + internalPoolsRouter.openErrorsScreen(message = t.message.orEmpty()) + } + } + + if (result.isNotEmpty()) { + coroutinesStore.uiScope.launch { +// internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) + internalPoolsRouter.back() + internalPoolsRouter.back() + internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.pl_liquidity_add_complete)) + } + } + }.invokeOnCompletion { + println("!!! remove confirm invokeOnCompletion") + setButtonLoading(false) + } + } + + private fun setButtonLoading(loading: Boolean) { + stateFlow.value = stateFlow.value.copy( + buttonLoading = loading + ) + } +} \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt new file mode 100644 index 0000000000..03299dea93 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -0,0 +1,180 @@ +package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm + +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import coil.compose.AsyncImage +import jp.co.soramitsu.common.compose.component.AccentButton +import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder +import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.compose.component.InfoTableItem +import jp.co.soramitsu.common.compose.component.MarginHorizontal +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.component.getImageRequest +import jp.co.soramitsu.common.compose.theme.backgroundBlack +import jp.co.soramitsu.common.compose.theme.customColors +import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.feature_wallet_impl.R + +data class LiquidityRemoveConfirmState( + val assetFromIconUrl: String? = null, + val assetToIconUrl: String? = null, + val baseAmount: String = "", + val baseFiat: String = "", + val targetAmount: String = "", + val targetFiat: String = "", + val feeInfo: FeeInfoViewState = FeeInfoViewState.default, + val buttonEnabled: Boolean = false, + val buttonLoading: Boolean = false +) + +interface LiquidityRemoveConfirmCallbacks { + + fun onRemoveConfirmClick() +} + +@Composable +fun LiquidityRemoveConfirmScreen( + state: LiquidityRemoveConfirmState, + callbacks: LiquidityRemoveConfirmCallbacks +) { + Column( + modifier = Modifier + .background(backgroundBlack) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp) + .padding(top = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + BackgroundCorneredWithBorder( + modifier = Modifier + .fillMaxWidth() + ) { + Column { + MarginVertical(margin = 16.dp) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetFromIconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = 7.dp) + .zIndex(1f) + ) + AsyncImage( + model = getImageRequest(LocalContext.current, state.assetToIconUrl.orEmpty()), + contentDescription = null, + modifier = Modifier + .size(64.dp) + .offset(x = (-7).dp) + .zIndex(0f) + ) + } + + Row( + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = state.baseAmount, + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End + ) + + MarginHorizontal(margin = 8.dp) + + Icon( + painter = painterResource(jp.co.soramitsu.common.R.drawable.ic_arrow_right_24), + contentDescription = null, + tint = MaterialTheme.customColors.white + ) + + MarginHorizontal(margin = 8.dp) + + Text( + text = state.targetAmount, + style = MaterialTheme.customTypography.header3, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start + ) + } + + MarginVertical(margin = 8.dp) + + InfoTableItem( + TitleValueViewState( + title = "Network fee", + value = state.feeInfo.feeAmount, + additionalValue = state.feeInfo.feeAmountFiat, + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) + ) + ) + } + } + + MarginVertical(margin = 24.dp) + } + + AccentButton( + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = "Confirm", + enabled = state.buttonEnabled, + loading = state.buttonLoading, + onClick = callbacks::onRemoveConfirmClick + ) + + MarginVertical(margin = 8.dp) + } +} + +@Preview +@Composable +private fun PreviewLiquidityRemoveConfirmScreen() { + LiquidityRemoveConfirmScreen( + state = LiquidityRemoveConfirmState( + feeInfo = FeeInfoViewState.default, + ), + callbacks = object : LiquidityRemoveConfirmCallbacks { + override fun onRemoveConfirmClick() {} + }, + ) +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt index 64b3a0df3b..2f342f54e8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt @@ -9,28 +9,19 @@ import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus class ValidateRemoveLiquidityUseCase @Inject constructor() { operator fun invoke( - assetFrom: AssetWithStatus, - assetTo: AssetWithStatus, - utilityAssetId: String, utilityAmount: BigDecimal, + userBasePooled: BigDecimal, + userTargetPooled: BigDecimal, amountFrom: BigDecimal, amountTo: BigDecimal, feeAmount: BigDecimal ): Result { return runCatching { - val isEnoughAmountFrom = amountFrom + feeAmount.takeIf { - assetFrom.asset.token.configuration.id == utilityAssetId - }.orZero() < assetFrom.asset.total.orZero() - - val isEnoughAmountTo = amountTo + feeAmount.takeIf { - assetTo.asset.token.configuration.id == utilityAssetId - }.orZero() < assetTo.asset.total.orZero() - - val isEnoughAmountFee = if (utilityAssetId in listOf(assetFrom.asset.token.configuration.id, assetTo.asset.token.configuration.id)) { - true - } else { - feeAmount < utilityAmount - } + val isEnoughAmountFrom = amountFrom <= userBasePooled + + val isEnoughAmountTo = amountTo <= userTargetPooled + + val isEnoughAmountFee = feeAmount < utilityAmount val validationChecks = mapOf ( TransferValidationResult.InsufficientBalance to (!isEnoughAmountFrom || !isEnoughAmountTo), diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index fc8c001772..e4ee2a3a80 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -99,6 +99,15 @@ interface PolkaswapRepository { // suspend fun updatePoolsSbApy() fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? + suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal + ): Result? + suspend fun observeAddLiquidity( chainId: ChainId, address: String, diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 918a622566..433c872cf8 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -936,6 +936,36 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT }.getOrThrow() } + + + override suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenFrom: Asset, + tokenTo: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal + ): Result? { + val soraChain = accountRepository.getChain(chainId) + val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId + val baseTokenId = tokenFrom.currencyId ?: return null + val targetTokenId = tokenTo.currencyId ?: return null + + return extrinsicService.submitExtrinsic( + chain = soraChain, + accountId = accountId + ) { + removeLiquidity( + dexId = getPoolBaseTokenDexId(chainId, baseTokenId), + outputAssetIdA = baseTokenId, + outputAssetIdB = targetTokenId, + markerAssetDesired = tokenFrom.planksFromAmount(markerAssetDesired), + outputAMin = tokenFrom.planksFromAmount(firstAmountMin), + outputBMin = tokenTo.planksFromAmount(secondAmountMin), + ) + } + } + override suspend fun observeAddLiquidity( chainId: ChainId, address: String, From 07caa6b7614319b3cdfb3d5ea28d02a5f82396bc Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 31 Jul 2024 17:52:50 +0500 Subject: [PATCH 36/84] shimmers; load improve; refactor --- .../jp/co/soramitsu/coredb/dao/PoolDao.kt | 4 +- .../domain/interfaces/PoolsInteractor.kt | 32 +-- .../navigation/InternalPoolsRouter.kt | 3 + .../liquiditypools/navigation/NavAction.kt | 5 + .../impl/domain/PoolsInteractorImpl.kt | 105 +++++---- .../navigation/InternalPoolsRouterImpl.kt | 19 ++ .../impl/presentation/PoolsFlowFragment.kt | 9 +- .../impl/presentation/PoolsFlowViewModel.kt | 15 +- .../allpools/AllPoolsPresenter.kt | 45 ++-- .../presentation/allpools/AllPoolsScreen.kt | 101 ++++++--- .../allpools/BasicPoolListItem.kt | 92 +++++++- .../liquidityadd/LiquidityAddPresenter.kt | 42 ++-- .../liquidityadd/LiquidityAddScreen.kt | 20 +- .../LiquidityAddConfirmPresenter.kt | 29 +-- .../LiquidityAddConfirmScreen.kt | 21 +- .../LiquidityRemovePresenter.kt | 5 + .../liquidityremove/LiquidityRemoveScreen.kt | 10 +- .../LiquidityRemoveConfirmPresenter.kt | 4 + .../LiquidityRemoveConfirmScreen.kt | 13 +- .../pooldetails/PoolDetailsPresenter.kt | 18 +- .../pooldetails/PoolDetailsScreen.kt | 13 +- .../poollist/PoolListPresenter.kt | 33 ++- .../presentation/poollist/PoolListScreen.kt | 2 + .../polkaswap/api/data/PolkaswapRepository.kt | 46 ++-- .../api/domain/models/BasicPoolData.kt | 42 +--- .../api/domain/models/UserPoolData.kt | 40 +--- .../sorablockexplorer/BlockExplorerManager.kt | 2 + feature-polkaswap-impl/build.gradle.kts | 1 + .../impl/data/PolkaswapRepositoryImpl.kt | 211 +++++++++++------- .../impl/data/network/blockchain/Extrinsic.kt | 22 +- .../impl/di/PolkaswapFeatureBindModule.kt | 7 +- 31 files changed, 610 insertions(+), 401 deletions(-) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt index 7857e216d1..dd284d0063 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt @@ -74,10 +74,10 @@ interface PoolDao { @Delete suspend fun deleteBasicPools(p: List) - @Upsert() + @Upsert suspend fun insertBasicPools(pools: List) - @Upsert() + @Upsert suspend fun insertUserPools(pools: List) } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 97c1b03e8f..073bfadee4 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -9,10 +9,14 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow interface PoolsInteractor { + val poolsChainId: String + suspend fun getBasicPools(chainId: ChainId): List +// fun subscribePoolsCache(): Flow> // suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? fun subscribePoolsCacheOfAccount(address: String): Flow> + fun subscribePoolsCacheCurrentAccount(): Flow> suspend fun getPoolData( chainId: ChainId, baseTokenId: String, @@ -29,10 +33,10 @@ interface PoolsInteractor { suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -40,14 +44,14 @@ interface PoolsInteractor { suspend fun calcRemoveLiquidityNetworkFee( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, ): BigDecimal? suspend fun isPairEnabled( chainId: ChainId, - inputTokenId: String, - outputTokenId: String + baseTokenId: String, + targetTokenId: String ): Boolean // suspend fun updateApy() @@ -56,10 +60,10 @@ interface PoolsInteractor { suspend fun observeAddLiquidity( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, - amountFrom: BigDecimal, - amountTo: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, enabled: Boolean, presented: Boolean, slippageTolerance: Double @@ -69,8 +73,8 @@ interface PoolsInteractor { suspend fun observeRemoveLiquidity( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, markerAssetDesired: BigDecimal, firstAmountMin: BigDecimal, secondAmountMin: BigDecimal, diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 961d10c7f4..e8956e2dae 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -31,5 +31,8 @@ interface InternalPoolsRouter { fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) fun openErrorsScreen(title: String? = null, message: String) + fun openInfoScreen(title: String, message: String) + fun openInfoScreen(itemId: Int) fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) + fun destination(clazz: Class): T? } \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt index a529ef119f..5da08397a6 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt @@ -7,4 +7,9 @@ sealed interface NavAction { val errorTitle: String?, val errorText: String ) : NavAction + + class ShowInfo( + val title: String, + val message: String + ) : NavAction } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 8951f99e79..4cfd676716 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -2,9 +2,11 @@ package jp.co.soramitsu.liquiditypools.impl.domain import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.data.secrets.v1.Keypair import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor @@ -15,7 +17,11 @@ import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.mapNotNull class PoolsInteractorImpl( private val polkaswapRepository: PolkaswapRepository, @@ -24,15 +30,35 @@ class PoolsInteractorImpl( private val chainRegistry: ChainRegistry, private val keypairProvider: KeypairProvider, ) : PoolsInteractor { + override val poolsChainId = soraTestChainId override suspend fun getBasicPools(chainId: ChainId): List { return polkaswapRepository.getBasicPools(chainId) } +// override fun subscribePoolsCache(): Flow> { +// return polkaswapRepository.subscribePools() +// } + override fun subscribePoolsCacheOfAccount(address: String): Flow> { return polkaswapRepository.subscribePools(address) } + private val soraPoolsAddressFlow = flowOf { + val meta = accountRepository.getSelectedMetaAccount() + println("!!! accountflow poolsChainId = $poolsChainId") + val chain = accountRepository.getChain(poolsChainId) + meta.address(chain) + }.mapNotNull { it } + .distinctUntilChanged() + + override fun subscribePoolsCacheCurrentAccount(): Flow> { + return soraPoolsAddressFlow.flatMapLatest { address -> + polkaswapRepository.subscribePools(address) + } + + } + override suspend fun getPoolData(chainId: ChainId, baseTokenId: String, targetTokenId: String): Flow { val address = accountRepository.getSelectedAccount(chainId).address return polkaswapRepository.subscribePool(address, baseTokenId, targetTokenId) @@ -63,10 +89,10 @@ class PoolsInteractorImpl( override suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -74,10 +100,10 @@ class PoolsInteractorImpl( return polkaswapRepository.calcAddLiquidityNetworkFee( chainId, address, - tokenFrom, - tokenTo, - tokenFromAmount, - tokenToAmount, + tokenBase, + tokenTarget, + tokenBaseAmount, + tokenTargetAmount, pairEnabled, pairPresented, slippageTolerance, @@ -86,49 +112,44 @@ class PoolsInteractorImpl( override suspend fun calcRemoveLiquidityNetworkFee( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, ): BigDecimal? { return polkaswapRepository.calcRemoveLiquidityNetworkFee( chainId, - tokenFrom, - tokenTo + tokenBase, + tokenTarget ) } -// override suspend fun updateApy() { -// polkaswapInteractor.updatePoolsSbApy() -// } - override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? = polkaswapInteractor.getPoolStrategicBonusAPY(reserveAccountOfPool) - override suspend fun isPairEnabled(chainId: ChainId, inputTokenId: String, outputTokenId: String): Boolean { - val dexId = polkaswapRepository.getPoolBaseTokenDexId(chainId, inputTokenId) + override suspend fun isPairEnabled(chainId: ChainId, baseTokenId: String, targetTokenId: String): Boolean { + val dexId = polkaswapRepository.getPoolBaseTokenDexId(chainId, baseTokenId) return polkaswapRepository.isPairAvailable( chainId, - inputTokenId, - outputTokenId, + baseTokenId, + targetTokenId, dexId ) } -// override suspend fun removeLiquidity( override suspend fun observeRemoveLiquidity( - chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, - markerAssetDesired: BigDecimal, - firstAmountMin: BigDecimal, - secondAmountMin: BigDecimal, - networkFee: BigDecimal + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + networkFee: BigDecimal ): String { val address = accountRepository.getSelectedAccount(chainId).address val status = polkaswapRepository.observeRemoveLiquidity( chainId, - tokenFrom, - tokenTo, + tokenBase, + tokenTarget, markerAssetDesired, firstAmountMin, secondAmountMin, @@ -140,10 +161,10 @@ class PoolsInteractorImpl( override suspend fun observeAddLiquidity( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, - amountFrom: BigDecimal, - amountTo: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, enabled: Boolean, presented: Boolean, slippageTolerance: Double @@ -154,10 +175,10 @@ class PoolsInteractorImpl( val networkFee = calcAddLiquidityNetworkFee( chainId, address, - tokenFrom, - tokenTo, - amountFrom, - amountTo, + tokenBase, + tokenTarget, + amountBase, + amountTarget, enabled, presented, slippageTolerance @@ -174,10 +195,10 @@ class PoolsInteractorImpl( chainId, address, keypair, - tokenFrom, - tokenTo, - amountFrom, - amountTo, + tokenBase, + tokenTarget, + amountBase, + amountTarget, enabled, presented, slippageTolerance diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 6169c83d4f..05d3d56366 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -3,6 +3,7 @@ package jp.co.soramitsu.liquiditypools.impl.navigation import java.math.BigDecimal import java.util.Stack import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction @@ -81,8 +82,26 @@ class InternalPoolsRouterImpl( mutableActionsFlow.tryEmit(NavAction.ShowError(title, message)) } + override fun openInfoScreen(title: String, message: String) { + mutableActionsFlow.tryEmit(NavAction.ShowInfo(title, message)) + } + + override fun openInfoScreen(itemId: Int) { + when (itemId) { + PoolsFlowViewModel.ITEM_APY_ID -> { + openInfoScreen("Strategic Bonus APY", "Farming reward for liquidity provision.") + } + PoolsFlowViewModel.ITEM_FEE_ID -> { + openInfoScreen("Network fee", "Network fee is used to ensure SORA system’s growth and stable performance. ") + } + } + } + override fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) { walletRouter.openOperationSuccess(txHash, chainId, customMessage) } + override fun destination(clazz: Class): T? { + return routesStack.filterIsInstance(clazz).lastOrNull() + } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 6aad843ecd..9be2e99df9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -46,6 +46,7 @@ import jp.co.soramitsu.common.compose.component.ToolbarBottomSheet import jp.co.soramitsu.common.compose.component.ToolbarHomeIconState import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.models.retrieveString +import jp.co.soramitsu.common.presentation.InfoDialog import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreen @@ -138,6 +139,12 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment + InfoDialog( + title = it.title, + message = it.message + ).show(childFragmentManager) } }.launchIn(this) } @@ -168,7 +175,7 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment = allPoolsPresenter.createScreenStateFlow(coroutinesStore.uiScope) @@ -133,11 +138,15 @@ class PoolsFlowViewModel @Inject constructor( ) LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName -> { -// val destinationArgs = innternalPoolsRouter.destination(LiquidityPoolsNavGraphRoute.ListPoolsScreen::class.java) -// val title = destinationArgs?.token?.title.orEmpty() + val destinationArgs = internalPoolsRouter.destination(LiquidityPoolsNavGraphRoute.ListPoolsScreen::class.java) + val title = if (destinationArgs?.isUserPools == true) { + "Your pools" + } else { + "All pools" + } LoadingState.Loaded( - TextModel.SimpleString("Your pools") + TextModel.SimpleString(title) ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 050dc4614a..4cb75c5bb4 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -5,22 +5,23 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -37,10 +38,6 @@ class AllPoolsPresenter @Inject constructor( private val accountInteractor: AccountInteractor, ) : AllPoolsScreenInterface { - private val _stateSlippage = MutableStateFlow(0.5) - val stateSlippage = _stateSlippage.asStateFlow() - - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) @@ -58,6 +55,7 @@ class AllPoolsPresenter @Inject constructor( chainsRepository.getChain(screenArgs.chainId) } + @OptIn(ExperimentalCoroutinesApi::class) val allPools = combine( accountInteractor.selectedMetaAccountFlow(), chainFlow @@ -65,15 +63,21 @@ class AllPoolsPresenter @Inject constructor( wallet.address(chain) } .mapNotNull { it } + .distinctUntilChanged() .flatMapLatest { address -> + println("!!! allPools flatMapLatest address = $address") poolsInteractor.subscribePoolsCacheOfAccount(address) }.map { pools -> - pools.sortedWith { o1, o2 -> - compareNullDesc(o1.basic.tvl, o2.basic.tvl) - }.groupBy { + println("!!! allPools subscribePoolsCacheOfAccount pools.size = ${pools.size}") + pools.groupBy { it.user != null + }.mapValues { + it.value.sortedWith { o1, o2 -> + compareNullDesc(o1.basic.tvl, o2.basic.tvl) + } } }.map { + println("!!! allPools grouped users = ${it[true]?.size}; other = ${it[false]?.size}") // it.map(BasicPoolData::toListItemState) it.mapValues { it -> it.value.mapNotNull { it.basic.toListItemState() } @@ -91,13 +95,11 @@ class AllPoolsPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { -// pools.onEach { -// stateFlow.value = stateFlow.value.copy(pools = it) -// }.launchIn(coroutineScope) - allPools.onEach { poolLists -> + println("!!! allPools subscribeState poolLists.size = ${poolLists.size}") + val userPools = poolLists[true].orEmpty() - val otherPools = poolLists[false]?.take(10).orEmpty() + val otherPools = poolLists[false].orEmpty() val shownUserPools = userPools.take(10) val shownOtherPools = otherPools.take(10) @@ -106,17 +108,17 @@ class AllPoolsPresenter @Inject constructor( val hasExtraAllPools = shownOtherPools.size < otherPools.size stateFlow.value = stateFlow.value.copy( - userPools = userPools, - allPools = otherPools, + userPools = shownUserPools, + allPools = shownOtherPools, hasExtraUserPools = hasExtraUserPools, - hasExtraAllPools = hasExtraAllPools + hasExtraAllPools = hasExtraAllPools, + isLoading = false ) }.launchIn(coroutineScope) } override fun onPoolClicked(pair: StringPair) { -// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return internalPoolsRouter.openDetailsPoolScreen(chainId, pair) } @@ -125,11 +127,4 @@ class AllPoolsPresenter @Inject constructor( val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return internalPoolsRouter.openPoolListScreen(chainId, isUserPools) } - - private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = - this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || - this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - this.token.configuration.id.lowercase().contains(filter.lowercase()) - - } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index a58036b3c2..2acc9971eb 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -41,6 +41,7 @@ data class AllPoolsState( val allPools: List = listOf(), val hasExtraUserPools: Boolean = false, val hasExtraAllPools: Boolean = false, + val isLoading: Boolean = true ) interface AllPoolsScreenInterface { @@ -64,52 +65,56 @@ fun AllPoolsScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - MarginVertical(margin = 16.dp) + MarginVertical(margin = 8.dp) - if (state.userPools.isNotEmpty()) { - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(0.dp), + if (state.isLoading) { + ShimmerPoolList() + } else { + if (state.userPools.isNotEmpty()) { + BackgroundCorneredWithBorder( modifier = Modifier - .wrapContentHeight() + .padding(horizontal = 16.dp) ) { - PoolGroupHeader( - title = stringResource(id = R.string.pl_your_pools), - onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } - ) - state.userPools.forEach { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier + .wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_your_pools), + onMoreClick = { callback.onMoreClick(true) } // todo restore .takeIf { state.hasExtraUserPools } ) + state.userPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } + MarginVertical(margin = 16.dp) } - MarginVertical(margin = 16.dp) - } - if (state.allPools.isNotEmpty()) { - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.wrapContentHeight() + if (state.allPools.isNotEmpty()) { + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) ) { - PoolGroupHeader( - title = stringResource(id = R.string.pl_all_pools), - onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } - ) - state.allPools.forEach { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier.wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_all_pools), + onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) + state.allPools.forEach { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } } @@ -118,6 +123,27 @@ fun AllPoolsScreen( } } +@Composable +fun ShimmerPoolList() { + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier.wrapContentHeight() + ) { + PoolGroupHeader( + title = stringResource(id = R.string.pl_all_pools), + onMoreClick = null + ) + repeat(10) { + BasicPoolShimmerItem(modifier = Modifier.padding(vertical = Dimens.x1)) + } + } + } +} + @Composable private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { Box(modifier = Modifier.wrapContentHeight()) { @@ -197,6 +223,7 @@ private fun PreviewAllPoolsScreen() { state = AllPoolsState( userPools = items, allPools = items, + isLoading = false ), callback = object : AllPoolsScreenInterface { override fun onPoolClicked(pair: StringPair) {} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index 98830a8438..dd5f698bb2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -3,12 +3,17 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.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.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -22,13 +27,19 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout +import com.valentinilk.shimmer.shimmer import jp.co.soramitsu.androidfoundation.format.StringPair -import jp.co.soramitsu.common.compose.theme.FearlessAppTheme +import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.Shimmer +import jp.co.soramitsu.common.compose.theme.alertYellow import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.customTypography +import jp.co.soramitsu.common.compose.theme.red +import jp.co.soramitsu.common.compose.theme.shimmerColor import jp.co.soramitsu.common.compose.theme.transparent import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white50 +import jp.co.soramitsu.shared_utils.icon.Circle import jp.co.soramitsu.ui_core.component.button.properties.Size import jp.co.soramitsu.ui_core.component.icons.TokenIcon @@ -145,6 +156,84 @@ fun BasicPoolListItem( } } +@Composable +fun BasicPoolShimmerItem( + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically, + ) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + ConstraintLayout( + modifier = Modifier + .wrapContentWidth() + .padding(start = 12.dp) + ) { + val (token1, token2) = createRefs() + Shimmer(Modifier + .size(Size.ExtraSmall) + .constrainAs(token1) { + top.linkTo(parent.top, 2.dp) + start.linkTo(parent.start) + bottom.linkTo(parent.bottom, 11.dp) + } + ) + + Shimmer(Modifier + .size(Size.ExtraSmall) + .constrainAs(token2) { + top.linkTo(parent.top, 11.dp) + start.linkTo(token1.start, margin = 16.dp) + bottom.linkTo(parent.bottom, 2.dp) + } + ) + } + } + Column( + modifier = Modifier +// .wrapContentHeight() + .weight(1f) + .padding(start = 8.dp, end = 12.dp), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Shimmer( + Modifier + .height(12.dp) + .width(70.dp)) + Shimmer( + Modifier + .height(12.dp) + .width(100.dp)) + } + MarginVertical(margin = 8.dp) + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Shimmer( + Modifier + .height(12.dp) + .width(70.dp)) + Shimmer( + Modifier + .height(12.dp) + .width(90.dp)) + } + } + } +} + @Preview @Composable @@ -174,5 +263,6 @@ private fun PreviewBasicPoolListItem() { text4 = "text4", ) ) + BasicPoolShimmerItem() } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 302c928abf..6ef22f3f03 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -86,10 +86,10 @@ class LiquidityAddPresenter @Inject constructor( .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) @OptIn(ExperimentalCoroutinesApi::class) - val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> + val assetsInPoolFlow = screenArgsFlow.distinctUntilChanged().flatMapLatest { screenArgs -> val ids = screenArgs.ids val chainId = screenArgs.chainId - println("!!! assetsInPoolFlow ids = $ids") + println("!!! assetsInPoolFlow ADD ids = $ids") val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { it.asset.token.configuration.currencyId == ids.first @@ -100,7 +100,7 @@ class LiquidityAddPresenter @Inject constructor( && it.asset.token.configuration.chainId == chainId } - println("!!! assetsInPoolFlow result% $firstInPair; $secondInPair") + println("!!! assetsInPoolFlow ADD result: $firstInPair; $secondInPair") if (firstInPair == null || secondInPair == null) { return@mapNotNull null } else { @@ -121,8 +121,10 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { + println("!!! AddPresenter subscribeState") screenArgsFlow.flatMapLatest { val (tokenFromId, tokenToId) = it.ids + println("!!! AddPresenter screenArgsFlow = $it") poolsInteractor.getPoolData(it.chainId, tokenFromId, tokenToId).onEach { stateFlow.value = stateFlow.value.copy( apy = it.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" } @@ -322,11 +324,11 @@ class LiquidityAddPresenter @Inject constructor( val isPoolPairEnabled = screenArgsFlow.map { screenArgs -> - val (tokenFromId, tokenToId) = screenArgs.ids + val (baseTokenId, targetTokenId) = screenArgs.ids poolsInteractor.isPairEnabled( screenArgs.chainId, - tokenFromId, - tokenToId + baseTokenId, + targetTokenId ) } @@ -337,14 +339,14 @@ class LiquidityAddPresenter @Inject constructor( stateSlippage, isPoolPairEnabled ) - { amountFrom, amountTo, (baseAsset, targetAsset), slippage, pairEnabled -> + { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> val networkFee = getLiquidityNetworkFee( - tokenFrom = baseAsset, - tokenTo = targetAsset, - tokenFromAmount = amountFrom, - tokenToAmount = amountTo, + tokenBase = baseAsset, + tokenTarget = targetAsset, + tokenBaseAmount = amountBase, + tokenToAmount = amountTarget, pairEnabled = pairEnabled, - pairPresented = true, //pairPresented, + pairPresented = true, slippageTolerance = slippage ) println("!!!! networkFeeFlow emit $networkFee") @@ -373,9 +375,9 @@ class LiquidityAddPresenter @Inject constructor( } private suspend fun getLiquidityNetworkFee( - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, tokenToAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, @@ -388,9 +390,9 @@ class LiquidityAddPresenter @Inject constructor( val result = poolsInteractor.calcAddLiquidityNetworkFee( chainId, address, - tokenFrom, - tokenTo, - tokenFromAmount, + tokenBase, + tokenTarget, + tokenBaseAmount, tokenToAmount, pairEnabled, pairPresented, @@ -478,6 +480,10 @@ class LiquidityAddPresenter @Inject constructor( } } + override fun onAddTableItemClick(itemId: Int) { + internalPoolsRouter.openInfoScreen(itemId) + } + private fun showError(throwable: Throwable) { when (throwable) { is ValidationException -> { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index 4f8bc1af14..65955efac9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -37,6 +37,9 @@ import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityAddState( val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, @@ -59,6 +62,8 @@ interface LiquidityAddCallbacks { fun onAddToAmountChange(amount: BigDecimal) fun onAddToAmountFocusChange(isFocused: Boolean) + + fun onAddTableItemClick(itemId: Int) } @Composable @@ -136,8 +141,11 @@ fun LiquidityAddScreen( TitleValueViewState( title = "Strategic bonus APY", value = state.apy, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) + ), + onClick = { + callbacks.onAddTableItemClick(ITEM_APY_ID) + } ) InfoTableItemAsset( TitleIconValueState( @@ -151,8 +159,11 @@ fun LiquidityAddScreen( title = "Network fee", value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) + ), + onClick = { + callbacks.onAddTableItemClick(ITEM_FEE_ID) + } ) } } @@ -192,6 +203,7 @@ private fun PreviewLiquidityAddScreen() { override fun onAddFromAmountFocusChange(isFocused: Boolean) {} override fun onAddToAmountChange(amount: BigDecimal) {} override fun onAddToAmountFocusChange(isFocused: Boolean) {} + override fun onAddTableItemClick(itemId: Int) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 040ee2e8d4..52d360de4b 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -202,23 +202,22 @@ class LiquidityAddConfirmPresenter @Inject constructor( setButtonLoading(true) coroutinesStore.ioScope.launch { val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch - val tokento = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch - val amountFrom = screenArgsFlow.firstOrNull()?.amountFrom.orZero() - val amountTo = screenArgsFlow.firstOrNull()?.amountTo.orZero() + val tokenBase = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch + val tokenTarget = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch + val amountBase = screenArgsFlow.firstOrNull()?.amountFrom.orZero() + val amountTarget = screenArgsFlow.firstOrNull()?.amountTo.orZero() val pairEnabled = isPoolPairEnabled.firstOrNull() ?: true - val pairPresented = true var result = "" try { result = poolsInteractor.observeAddLiquidity( - chainId, - tokenFrom, - tokento, - amountFrom, - amountTo, - pairEnabled, - pairPresented, - _stateSlippage.value, + chainId = chainId, + tokenBase = tokenBase, + tokenTarget = tokenTarget, + amountBase = amountBase, + amountTarget = amountTarget, + enabled = pairEnabled, + presented = true, + slippageTolerance = _stateSlippage.value, ) } catch (t: Throwable) { coroutinesStore.uiScope.launch { @@ -240,6 +239,10 @@ class LiquidityAddConfirmPresenter @Inject constructor( } } + override fun onAddItemClick(itemId: Int) { + internalPoolsRouter.openInfoScreen(itemId) + } + private fun setButtonLoading(loading: Boolean) { stateFlow.value = stateFlow.value.copy( buttonLoading = loading diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index bee49245a1..629ff78b27 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -44,6 +44,9 @@ import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white50 import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityAddConfirmState( val assetFrom: Asset? = null, @@ -62,6 +65,7 @@ data class LiquidityAddConfirmState( interface LiquidityAddConfirmCallbacks { fun onConfirmClick() + fun onAddItemClick(itemId: Int) } @Composable @@ -85,12 +89,12 @@ fun LiquidityAddConfirmScreen( MarginVertical(margin = 16.dp) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - AsyncImage( + AsyncImage( model = getImageRequest(LocalContext.current, state.assetFrom?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = 7.dp) + .offset(x = 14.dp) .zIndex(1f) ) AsyncImage( @@ -98,7 +102,7 @@ fun LiquidityAddConfirmScreen( contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = (-7).dp) + .offset(x = (-14).dp) .zIndex(0f) ) } @@ -159,8 +163,9 @@ fun LiquidityAddConfirmScreen( TitleValueViewState( title = "Strategic bonus APY", value = state.apy, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) + ), + onClick = { callbacks.onAddItemClick(ITEM_APY_ID)} ) InfoTableItemAsset( TitleIconValueState( @@ -174,8 +179,9 @@ fun LiquidityAddConfirmScreen( title = "Network fee", value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) + ), + onClick = { callbacks.onAddItemClick(ITEM_FEE_ID)} ) InfoTableItem( TitleValueViewState( @@ -224,6 +230,7 @@ private fun PreviewLiquidityAddConfirmScreen() { ), callbacks = object : LiquidityAddConfirmCallbacks { override fun onConfirmClick() {} + override fun onAddItemClick(itemId: Int) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 12f15ca8e6..e17dbb3d1c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -23,6 +23,7 @@ import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateRemoveLiquidityUseCase import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute @@ -547,6 +548,10 @@ class LiquidityRemovePresenter @Inject constructor( isToAmountFocused.value = isFocused } + override fun onRemoveItemClick(itemId: Int) { + internalPoolsRouter.openInfoScreen(itemId) + } + private fun showError(throwable: Throwable) { when (throwable) { is ValidationException -> { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index e022666491..711622d516 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -37,6 +37,8 @@ import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityRemoveState( val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, @@ -59,6 +61,8 @@ interface LiquidityRemoveCallbacks { fun onRemoveToAmountChange(amount: BigDecimal) fun onRemoveToAmountFocusChange(isFocused: Boolean) + + fun onRemoveItemClick(itemId: Int) } @Composable @@ -143,8 +147,9 @@ fun LiquidityRemoveScreen( title = "Network fee", value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) + ), + onClick = { callbacks.onRemoveItemClick(ITEM_FEE_ID) } ) } } @@ -182,6 +187,7 @@ private fun PreviewLiquidityRemoveScreen() { override fun onRemoveFromAmountFocusChange(isFocused: Boolean) {} override fun onRemoveToAmountChange(amount: BigDecimal) {} override fun onRemoveToAmountFocusChange(isFocused: Boolean) {} + override fun onRemoveItemClick(itemId: Int) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 0cbe3f6f95..ae4b14669b 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -236,6 +236,10 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } } + override fun onRemoveConfirmItemClick(itemId: Int) { + internalPoolsRouter.openInfoScreen(itemId) + } + private fun setButtonLoading(loading: Boolean) { stateFlow.value = stateFlow.value.copy( buttonLoading = loading diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt index 03299dea93..497ff59a1c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -38,6 +38,8 @@ import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.customColors import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityRemoveConfirmState( val assetFromIconUrl: String? = null, @@ -54,6 +56,7 @@ data class LiquidityRemoveConfirmState( interface LiquidityRemoveConfirmCallbacks { fun onRemoveConfirmClick() + fun onRemoveConfirmItemClick(itemId: Int) } @Composable @@ -88,7 +91,7 @@ fun LiquidityRemoveConfirmScreen( contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = 7.dp) + .offset(x = 14.dp) .zIndex(1f) ) AsyncImage( @@ -96,7 +99,7 @@ fun LiquidityRemoveConfirmScreen( contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = (-7).dp) + .offset(x = (-14).dp) .zIndex(0f) ) } @@ -142,8 +145,9 @@ fun LiquidityRemoveConfirmScreen( title = "Network fee", value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 2) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) + ), + onClick = { callbacks.onRemoveConfirmItemClick(ITEM_FEE_ID) } ) } } @@ -175,6 +179,7 @@ private fun PreviewLiquidityRemoveConfirmScreen() { ), callbacks = object : LiquidityRemoveConfirmCallbacks { override fun onRemoveConfirmClick() {} + override fun onRemoveConfirmItemClick(itemId: Int) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index 6e0a162eda..b56ec0d2b1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -10,6 +10,7 @@ import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.formatPercent import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData @@ -18,6 +19,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -54,6 +56,7 @@ class PoolDetailsPresenter @Inject constructor( return stateFlow } + @OptIn(ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { screenArgsFlow.flatMapLatest { observePoolDetails(it.chainId, it.ids).onEach { @@ -83,9 +86,13 @@ class PoolDetailsPresenter @Inject constructor( } + override fun onDetailItemClick(itemId: Int) { + internalPoolsRouter.openInfoScreen(itemId) + } + suspend fun observePoolDetails(chainId: ChainId, ids: StringPair): Flow { - val (tokenFromId, tokenToId) = ids - return poolsInteractor.getPoolData(chainId, tokenFromId, tokenToId) + val (baseTokenId, targetTokenId) = ids + return poolsInteractor.getPoolData(chainId, baseTokenId, targetTokenId) } suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { @@ -108,13 +115,6 @@ class PoolDetailsPresenter @Inject constructor( } return result } - - private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = - this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || - this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - this.token.configuration.id.lowercase().contains(filter.lowercase()) - - } private fun CommonPoolData.mapToState(): PoolDetailsState { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 119ae4c8ac..1df957a9fe 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -40,6 +40,8 @@ import jp.co.soramitsu.common.compose.theme.customColors import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.feature_wallet_impl.R +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel +import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID data class PoolDetailsState( val assetFrom: Asset? = null, @@ -55,6 +57,7 @@ data class PoolDetailsState( interface PoolDetailsCallbacks { fun onSupplyLiquidityClick() fun onRemoveLiquidityClick() + fun onDetailItemClick(itemId: Int) } @Composable @@ -82,7 +85,7 @@ fun PoolDetailsScreen( contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = 7.dp) + .offset(x = 14.dp) .zIndex(1f) ) AsyncImage( @@ -90,7 +93,7 @@ fun PoolDetailsScreen( contentDescription = null, modifier = Modifier .size(64.dp) - .offset(x = (-7).dp) + .offset(x = (-14).dp) .zIndex(0f) ) } @@ -140,8 +143,9 @@ fun PoolDetailsScreen( TitleValueViewState( title = "Strategic bonus APY", value = state.apy, - clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, 1) - ) + clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) + ), + onClick = { callbacks.onDetailItemClick(ITEM_APY_ID) } ) InfoTableItemAsset( TitleIconValueState( @@ -210,6 +214,7 @@ private fun PreviewPoolDetailsScreen() { callbacks = object : PoolDetailsCallbacks { override fun onSupplyLiquidityClick() {} override fun onRemoveLiquidityClick() {} + override fun onDetailItemClick(itemId: Int) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 2bf8033b78..5c8d20ac4b 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -4,7 +4,6 @@ import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState @@ -41,22 +40,24 @@ class PoolListPresenter @Inject constructor( .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) - val chainFlow = screenArgsFlow.map { screenArgs -> - chainsRepository.getChain(screenArgs.chainId) - } - val pools = screenArgsFlow.flatMapLatest { screenArgs -> combine( - flowOf { poolsInteractor.getBasicPools(screenArgs.chainId) }, + poolsInteractor.subscribePoolsCacheCurrentAccount(), enteredAssetQueryFlow ) { pools, query -> pools.filter { - it.isFilterMatch(query) + if (screenArgs.isUserPools) { + it.user != null + } else { + true + } + }.filter { + it.basic.isFilterMatch(query) }.sortedWith { o1, o2 -> - compareNullDesc(o1.tvl, o2.tvl) + compareNullDesc(o1.basic.tvl, o2.basic.tvl) } }.map { - it.mapNotNull(BasicPoolData::toListItemState) + it.mapNotNull{ it.basic.toListItemState() } } } @@ -76,24 +77,18 @@ class PoolListPresenter @Inject constructor( pools.onEach { stateFlow.value = stateFlow.value.copy(pools = it) }.launchIn(coroutineScope) - } + enteredAssetQueryFlow.onEach { + stateFlow.value = stateFlow.value.copy(searchQuery = it) + }.launchIn(coroutineScope) + } override fun onPoolClicked(pair: StringPair) { -// val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return internalPoolsRouter.openDetailsPoolScreen(chainId, pair) } - override fun onAssetSearchEntered(value: String) { enteredAssetQueryFlow.value = value } - - private fun jp.co.soramitsu.wallet.impl.domain.model.Asset.isMatchFilter(filter: String): Boolean = - this.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || - this.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - this.token.configuration.id.lowercase().contains(filter.lowercase()) - - } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 6c9e71d924..007d3f6fc6 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.CorneredInput +import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.theme.white04 import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItem @@ -41,6 +42,7 @@ fun PoolListScreen( Column( modifier = Modifier.fillMaxSize(), ) { + MarginVertical(margin = 16.dp) Box( contentAlignment = Alignment.CenterStart, modifier = Modifier.padding(horizontal = 16.dp) diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index e4ee2a3a80..bed8608d49 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -1,16 +1,16 @@ package jp.co.soramitsu.polkaswap.api.data import java.math.BigDecimal -import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse -import jp.co.soramitsu.polkaswap.api.models.Market -import jp.co.soramitsu.polkaswap.api.models.WithDesired -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import kotlinx.coroutines.flow.Flow import java.math.BigInteger import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.polkaswap.api.models.Market +import jp.co.soramitsu.polkaswap.api.models.WithDesired +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair +import kotlinx.coroutines.flow.Flow interface PolkaswapRepository { suspend fun getAvailableDexes(chainId: ChainId): List @@ -63,27 +63,20 @@ interface PolkaswapRepository { suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? -// suspend fun getPoolOfAccount( -// address: String?, -// tokenFromId: String, -// tokenToId: String, -// chainId: String -// ): CommonUserPoolData? -// suspend fun getUserPoolData( chainId: ChainId, address: String, baseTokenId: String, - tokenId: ByteArray + targetTokenId: ByteArray ): PoolDataDto? suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -91,18 +84,18 @@ interface PolkaswapRepository { suspend fun calcRemoveLiquidityNetworkFee( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, ): BigDecimal? suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int -// suspend fun updatePoolsSbApy() + fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? suspend fun observeRemoveLiquidity( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, markerAssetDesired: BigDecimal, firstAmountMin: BigDecimal, secondAmountMin: BigDecimal @@ -112,10 +105,10 @@ interface PolkaswapRepository { chainId: ChainId, address: String, keypair: Keypair, - tokenFrom: Asset, - tokenTo: Asset, - amountFrom: BigDecimal, - amountTo: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -124,6 +117,7 @@ interface PolkaswapRepository { suspend fun updateAccountPools(chainId: ChainId, address: String) suspend fun updateBasicPools(chainId: ChainId) +// fun subscribePools(): Flow> fun subscribePools(address: String): Flow> fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt index 1e2b94e663..f26740856a 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt @@ -1,13 +1,9 @@ package jp.co.soramitsu.polkaswap.api.domain.models -import android.os.Parcelable import java.math.BigDecimal import jp.co.soramitsu.wallet.impl.domain.model.Asset -import kotlinx.parcelize.Parcelize data class BasicPoolData( -// val baseToken: Token, -// val targetToken: Token, val baseToken: Asset, val targetToken: Asset?, val baseReserves: BigDecimal, @@ -16,42 +12,6 @@ data class BasicPoolData( val reserveAccount: String, val sbapy: Double?, ) { - val fiatSymbol = "baseToken.fiatSymbol" val tvl: BigDecimal? get() = baseToken.token.fiatRate?.times(BigDecimal(2))?.multiply(baseReserves) -} - -@Parcelize -data class Token( - val id: String, - val name: String, - val symbol: String, - val precision: Int, - val isHidable: Boolean, - val iconFile: String?, - val fiatPrice: Double?, - val fiatPriceChange: Double?, - val fiatSymbol: String?, -) : Parcelable { - -// fun printBalance( -// balance: BigDecimal, -// nf: NumbersFormatter, -// precision: Int = this.precision -// ): String = -// String.format("%s %s", nf.formatBigDecimal(balance, precision), symbol) - -// fun printBalanceWithConstrainedLength( -// balance: BigDecimal, -// nf: NumbersFormatter, -// length: Int = 8 -// ): String { -// val integerLength = balance.toBigInteger().toString().length -// var newPrecision = length - integerLength -// if (newPrecision <= 0) { -// newPrecision = 1 -// } -// -// return printBalance(balance, nf, newPrecision) -// } -} +} \ No newline at end of file diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt index b4a40d74d0..8df483eb71 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt @@ -5,16 +5,12 @@ import java.math.BigDecimal data class CommonUserPoolData( val basic: BasicPoolData, val user: UserPoolData, -) { - fun printFiat(): Pair? = printFiatInternal(basic, user) -} +) data class CommonPoolData( val basic: BasicPoolData, val user: UserPoolData?, -) { - fun printFiat(): Pair? = printFiatInternal(basic, user) -} +) data class UserPoolData( // val address: String?, @@ -24,34 +20,14 @@ data class UserPoolData( val poolProvidersBalance: BigDecimal, ) -val List.fiatSymbol: String - get() { - return getOrNull(0)?.basic?.fiatSymbol ?: "$" //OptionsProvider.fiatSymbol - } - -private fun printFiatInternal(basic: BasicPoolData, user: UserPoolData?): Pair? { - return null -// if (user == null) return null -// val f1 = user.basePooled.applyFiatRate(basic.baseToken.token.fiatRate) -// val f2 = user.targetPooled.applyFiatRate(basic.targetToken?.token?.fiatRate) -// if (f1 == null || f2 == null) return null -// val change1 = basic.baseToken.fiatPriceChange ?: 0.0 -// val change2 = basic.targetToken.fiatPriceChange ?: 0.0 -// val price1 = basic.baseToken.fiatPrice ?: return null -// val price2 = basic.targetToken.fiatPrice ?: return null -// val newPoolFiat = f1 + f2 -// val oldPoolFiat = calcAmount(price1 / (1 + change1), user.basePooled) + -// calcAmount(price2 / (1 + change2), user.targetPooled) -// val changePool = fiatChange(oldPoolFiat, newPoolFiat) -// return newPoolFiat to changePool -} - fun BasicPoolData.isFilterMatch(filter: String): Boolean { - val t1 = targetToken?.token?.configuration?.name?.lowercase()?.contains(filter.lowercase()) == true || + val t1 = +// targetToken?.token?.configuration?.name?.lowercase()?.contains(filter.lowercase()) == true || targetToken?.token?.configuration?.symbol?.lowercase()?.contains(filter.lowercase()) == true || - targetToken?.token?.configuration?.id?.lowercase()?.contains(filter.lowercase()) == true - val t2 = baseToken.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || + targetToken?.token?.configuration?.currencyId?.lowercase()?.contains(filter.lowercase()) == true + val t2 = +// baseToken.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || baseToken.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - baseToken.token.configuration.id.lowercase().contains(filter.lowercase()) + baseToken.token.configuration.currencyId?.lowercase()?.contains(filter.lowercase()) == true return t1 || t2 } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt index 647a0348fd..586eccb337 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt @@ -23,8 +23,10 @@ class BlockExplorerManager @Inject constructor( private suspend fun updateSbApyInternal() { runCatching { val response = info.getSpApy() + println("!!! call blockExplorerManager.updatePoolsSbApy() result size = ${response.size}") tempApy.clear() tempApy.addAll(response) + println("!!! call blockExplorerManager.updatePoolsSbApy() result updated") } } diff --git a/feature-polkaswap-impl/build.gradle.kts b/feature-polkaswap-impl/build.gradle.kts index dc0800219c..b892a6ca99 100644 --- a/feature-polkaswap-impl/build.gradle.kts +++ b/feature-polkaswap-impl/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(libs.fragmentKtx) implementation(libs.material) implementation(libs.room.runtime) + implementation(libs.room.ktx) implementation(libs.xnetworking.basic) implementation(libs.xnetworking.sorawallet) { exclude(group = "jp.co.soramitsu.xnetworking", module = "basic") diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 433c872cf8..a0b5a62939 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -1,5 +1,7 @@ package jp.co.soramitsu.polkaswap.impl.data +import androidx.room.Transaction +import androidx.room.withTransaction import java.math.BigDecimal import java.math.BigInteger import javax.inject.Inject @@ -12,7 +14,9 @@ import jp.co.soramitsu.common.data.network.config.PolkaswapRemoteConfig import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.dexManager +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.fromHex +import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.common.utils.poolTBC import jp.co.soramitsu.common.utils.poolXYK import jp.co.soramitsu.common.utils.u32ArgumentFromStorageKey @@ -21,6 +25,7 @@ import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.coredb.AppDatabase import jp.co.soramitsu.coredb.dao.PoolDao import jp.co.soramitsu.coredb.model.BasicPoolLocal import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal @@ -82,12 +87,14 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach class PolkaswapRepositoryImpl @Inject constructor( private val remoteConfigFetcher: RemoteConfigFetcher, @@ -99,6 +106,7 @@ class PolkaswapRepositoryImpl @Inject constructor( private val walletRepository: WalletRepository, private val blockExplorerManager: BlockExplorerManager, private val poolDao: PoolDao, + private val db: AppDatabase, ) : PolkaswapRepository { override suspend fun getAvailableDexes(chainId: ChainId): List { @@ -448,12 +456,12 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" val element = BasicPoolData( - asset, - targetAsset, - mapBalance(reserves[0], asset.token.configuration.precision), - mapBalance(reserves[1], asset.token.configuration.precision), - total ?: BigDecimal.ZERO, - reserveAccountAddress, + baseToken = asset, + targetToken = targetAsset, + baseReserves = mapBalance(reserves[0], asset.token.configuration.precision), + targetReserves = mapBalance(reserves[1], asset.token.configuration.precision), + totalIssuance = total ?: BigDecimal.ZERO, + reserveAccount = reserveAccountAddress, sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) ) @@ -502,11 +510,11 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT chainId: ChainId, address: String, baseTokenId: String, - tokenId: ByteArray + targetTokenId: ByteArray ): PoolDataDto? { - val reserves = getPairWithXorReserves(chainId, baseTokenId, tokenId) + val reserves = getPairWithXorReserves(chainId, baseTokenId, targetTokenId) val totalIssuanceAndProperties = - getPoolTotalIssuanceAndProperties(chainId, baseTokenId, tokenId, address) + getPoolTotalIssuanceAndProperties(chainId, baseTokenId, targetTokenId, address) if (reserves == null || totalIssuanceAndProperties == null) { return null @@ -515,7 +523,7 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT return PoolDataDto( baseTokenId, - tokenId.toHexString(true), + targetTokenId.toHexString(true), reserves.first, reserves.second, totalIssuanceAndProperties.first, @@ -527,29 +535,30 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT override suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double ): BigDecimal? { - val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenFromAmount, slippageTolerance) - val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenToAmount, slippageTolerance) - val dexId = getPoolBaseTokenDexId(chainId, tokenFrom.currencyId) + val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenBaseAmount, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenTargetAmount, slippageTolerance) + val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) val chain = chainRegistry.getChain(chainId) + val fee = extrinsicService.estimateFee(chain) { liquidityAdd( - dexId, - tokenFrom, - tokenTo, - pairPresented, - pairEnabled, - tokenFromAmount, - tokenToAmount, - amountFromMin, - amountToMin + dexId = dexId, + baseTokenId = tokenBase.currencyId, + targetTokenId = tokenTarget.currencyId, + pairPresented = pairPresented, + pairEnabled = pairEnabled, + tokenBaseAmount = tokenBase.planksFromAmount(tokenBaseAmount), + tokenTargetAmount = tokenTarget.planksFromAmount(tokenTargetAmount), + amountBaseMin = tokenBase.planksFromAmount(amountFromMin), + amountTargetMin = tokenTarget.planksFromAmount(amountToMin), ) } @@ -559,21 +568,21 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT override suspend fun calcRemoveLiquidityNetworkFee( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, ): BigDecimal? { val chain = chainRegistry.getChain(chainId) - val baseTokenId = tokenFrom.currencyId ?: return null - val targetTokenId = tokenTo.currencyId ?: return null + val baseTokenId = tokenBase.currencyId ?: return null + val targetTokenId = tokenTarget.currencyId ?: return null val fee = extrinsicService.estimateFee(chain) { removeLiquidity( dexId = getPoolBaseTokenDexId(chainId, baseTokenId), outputAssetIdA = baseTokenId, outputAssetIdB = targetTokenId, - markerAssetDesired = tokenFrom.planksFromAmount(BigDecimal.ONE), - outputAMin = tokenFrom.planksFromAmount(BigDecimal.ONE), - outputBMin = tokenTo.planksFromAmount(BigDecimal.ONE), + markerAssetDesired = tokenBase.planksFromAmount(BigDecimal.ONE), + outputAMin = tokenBase.planksFromAmount(BigDecimal.ONE), + outputBMin = tokenTarget.planksFromAmount(BigDecimal.ONE), ) } val feeToken = chain.utilityAsset @@ -616,7 +625,6 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT } } - fun Struct.Instance.mapToToken(field: String) = this.get(field)?.getTokenId()?.toHexString(true) @@ -930,26 +938,21 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT storageKey.assetIdFromKey() to tokens } } - }.onFailure { - println("!!! getUserPoolsTokenIds onFailure") -// it.printStackTrace() }.getOrThrow() } - - override suspend fun observeRemoveLiquidity( chainId: ChainId, - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, markerAssetDesired: BigDecimal, firstAmountMin: BigDecimal, secondAmountMin: BigDecimal ): Result? { val soraChain = accountRepository.getChain(chainId) val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId - val baseTokenId = tokenFrom.currencyId ?: return null - val targetTokenId = tokenTo.currencyId ?: return null + val baseTokenId = tokenBase.currencyId ?: return null + val targetTokenId = tokenTarget.currencyId ?: return null return extrinsicService.submitExtrinsic( chain = soraChain, @@ -959,9 +962,9 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT dexId = getPoolBaseTokenDexId(chainId, baseTokenId), outputAssetIdA = baseTokenId, outputAssetIdB = targetTokenId, - markerAssetDesired = tokenFrom.planksFromAmount(markerAssetDesired), - outputAMin = tokenFrom.planksFromAmount(firstAmountMin), - outputBMin = tokenTo.planksFromAmount(secondAmountMin), + markerAssetDesired = tokenBase.planksFromAmount(markerAssetDesired), + outputAMin = tokenBase.planksFromAmount(firstAmountMin), + outputBMin = tokenTarget.planksFromAmount(secondAmountMin), ) } } @@ -970,23 +973,22 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT chainId: ChainId, address: String, keypair: Keypair, - tokenFrom: Asset, - tokenTo: Asset, - amountFrom: BigDecimal, - amountTo: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double ): Result? { -// ): Pair? { - val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountFrom, slippageTolerance) - val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTo, slippageTolerance) - val dexId = getPoolBaseTokenDexId(chainId, tokenFrom.currencyId) + val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountBase, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTarget, slippageTolerance) + val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) val soraChain = accountRepository.getChain(chainId) val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId - val baseTokenId = tokenFrom.currencyId - val targetTokenId = tokenTo.currencyId + val baseTokenId = tokenBase.currencyId + val targetTokenId = tokenTarget.currencyId if (baseTokenId == null || targetTokenId == null) return null return extrinsicService.submitExtrinsic( @@ -1013,10 +1015,10 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT dexId = dexId, baseAssetId = baseTokenId, targetAssetId = targetTokenId, - baseAssetAmount = mapBalance(amountFrom, tokenFrom.precision), - targetAssetAmount = mapBalance(amountTo, tokenTo.precision), - amountFromMin = mapBalance(amountFromMin, tokenFrom.precision), - amountToMin = mapBalance(amountToMin, tokenTo.precision) + baseAssetAmount = mapBalance(amountBase, tokenBase.precision), + targetAssetAmount = mapBalance(amountTarget, tokenTarget.precision), + amountFromMin = mapBalance(amountFromMin, tokenBase.precision), + amountToMin = mapBalance(amountToMin, tokenTarget.precision) ) } } @@ -1083,17 +1085,26 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT } } - poolDao.clearTable(address) - poolDao.insertBasicPools( - pools.map { + db.withTransaction { + println("!!! updateAccountPools: poolDao.clearTable(address)") + poolDao.clearTable(address) + println("!!! updateAccountPools: poolDao.insertBasicPools() size = ${pools.map { it.basicPoolLocal - } - ) - poolDao.insertUserPools( - pools.map { + }.size}") + poolDao.insertBasicPools( + pools.map { + it.basicPoolLocal + } + ) + println("!!! updateAccountPools: poolDao.insertUSERPools() size = ${pools.map { it.userPoolLocal - } - ) + }.size}") + poolDao.insertUserPools( + pools.map { + it.userPoolLocal + } + ) + } } override suspend fun updateBasicPools(chainId: ChainId) { @@ -1130,7 +1141,6 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT mapBalance(it, asset.precision) } - val targetAsset = assets.firstOrNull { it.currencyId == targetToken } val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" list.add( @@ -1154,22 +1164,60 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT poolDao.deleteBasicPools(minus) poolDao.insertBasicPools(list) } + val assetsFlow = accountRepository.selectedMetaAccountFlow() + .distinctUntilChanged { old, new -> old.id != new.id } + .flatMapLatest { meta -> + walletRepository.assetsFlow(meta) + }.mapList { it.asset } @OptIn(FlowPreview::class) override fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow { - return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { - mapPoolLocalToData(it) - }.debounce(500) + return combine( + poolDao.subscribePool(address, baseTokenId, targetTokenId), + assetsFlow + ) { pool, assets -> + mapPoolLocalToData(pool, assets) + } + .mapNotNull { it } + .debounce(500) + +// return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// mapPoolLocalToData(it, assets) +// }.debounce(500) } @OptIn(FlowPreview::class) override fun subscribePools(address: String): Flow> { -// return poolDao.subscribePoolsList(address).map { pools -> - return poolDao.subscribeAllPools(address).map { pools -> - pools.mapNotNull { poolLocal -> - mapPoolLocalToData(poolLocal) + println("!!! repoImpl call subscribePools for address: $address") + + return combine( + poolDao.subscribeAllPools(address), + assetsFlow + ) { pools, assets -> + println("!!! repoImpl subscribePools pools: ${pools.size}") + val mapNotNull = pools.mapNotNull { poolLocal -> + mapPoolLocalToData(poolLocal, assets) + } + println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") + mapNotNull + } + .debounce(500) + +// return poolDao.subscribeAllPools(address).map { pools -> +// println("!!! repoImpl subscribePools pools: ${pools.size}") +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// val mapNotNull = pools.mapNotNull { poolLocal -> +// mapPoolLocalToData(poolLocal, assets) +// } +// println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") +// mapNotNull +// }.debounce(500) + .onEach { + println("!!! repoImpl .debounce(500) pools: ${it.size}") } - }.debounce(500) } fun RuntimeSnapshot.accountPoolsKey(address: String): String = @@ -1177,13 +1225,10 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT .storage("AccountPools") .storageKey(this, address.toAccountId()) - private suspend fun mapPoolLocalToData( + private fun mapPoolLocalToData( poolLocal: UserPoolJoinedLocalNullable, -// poolLocal: UserPoolJoinedLocal, + assets: List ): CommonPoolData? { - val metaId = accountRepository.getSelectedLightMetaAccount().id - val assets = walletRepository.getAssets(metaId) - val baseToken = assets.firstOrNull { it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdBase } ?: return null diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt index ba4bcf3c73..843115411a 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt @@ -124,17 +124,15 @@ fun ExtrinsicBuilder.removeLiquidity( fun ExtrinsicBuilder.liquidityAdd( dexId: Int, - tokenFrom: Asset, - tokenTo: Asset, + baseTokenId: String?, + targetTokenId: String?, pairPresented: Boolean, pairEnabled: Boolean, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, - amountFromMin: BigDecimal, - amountToMin: BigDecimal + tokenBaseAmount: BigInteger, + tokenTargetAmount: BigInteger, + amountBaseMin: BigInteger, + amountTargetMin: BigInteger ) { - val baseTokenId = tokenFrom.currencyId - val targetTokenId = tokenTo.currencyId if (baseTokenId != null && targetTokenId != null) { if (!pairPresented) { if (!pairEnabled) { @@ -155,10 +153,10 @@ fun ExtrinsicBuilder.liquidityAdd( dexId = dexId, baseTokenId, targetTokenId, - tokenFrom.planksFromAmount(tokenFromAmount), - tokenTo.planksFromAmount(tokenToAmount), - tokenFrom.planksFromAmount(amountFromMin), - tokenTo.planksFromAmount(amountToMin), + tokenBaseAmount, + tokenTargetAmount, + amountBaseMin, + amountTargetMin, ) } } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 961c54582e..211949cf0f 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -24,6 +24,7 @@ import jp.co.soramitsu.runtime.di.REMOTE_STORAGE_SOURCE import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.IChainRegistry +import jp.co.soramitsu.coredb.AppDatabase import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.coredb.dao.PoolDao @@ -56,7 +57,8 @@ class PolkaswapFeatureModule { accountRepository: AccountRepository, walletRepository: WalletRepository, sorablockexplorer: BlockExplorerManager, - poolDao: PoolDao + poolDao: PoolDao, + appDataBase: AppDatabase ): PolkaswapRepository { return PolkaswapRepositoryImpl( remoteConfigFetcher, @@ -67,7 +69,8 @@ class PolkaswapFeatureModule { accountRepository, walletRepository, sorablockexplorer, - poolDao + poolDao, + appDataBase ) } From fe9d2be8a51cdc32b70fce73307c62d97fe78386 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 31 Jul 2024 21:16:24 +0500 Subject: [PATCH 37/84] pair rename --- .../navigation/InternalPoolsRouter.kt | 6 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 8 +- .../navigation/InternalPoolsRouterImpl.kt | 10 +- .../presentation/allpools/AllPoolsScreen.kt | 6 +- .../liquidityadd/LiquidityAddPresenter.kt | 180 ++++++++-------- .../liquidityadd/LiquidityAddScreen.kt | 37 ++-- .../LiquidityAddConfirmPresenter.kt | 64 +++--- .../LiquidityAddConfirmScreen.kt | 17 +- .../LiquidityRemovePresenter.kt | 193 ++++++++++-------- .../liquidityremove/LiquidityRemoveScreen.kt | 39 ++-- .../LiquidityRemoveConfirmPresenter.kt | 77 ++++--- .../LiquidityRemoveConfirmScreen.kt | 9 +- .../pooldetails/PoolDetailsPresenter.kt | 9 +- .../pooldetails/PoolDetailsScreen.kt | 17 +- .../poollist/PoolListPresenter.kt | 10 +- .../presentation/poollist/PoolListScreen.kt | 34 +-- .../usecase/ValidateAddLiquidityUseCase.kt | 24 +-- .../usecase/ValidateRemoveLiquidityUseCase.kt | 12 +- .../impl/data/PolkaswapRepositoryImpl.kt | 5 +- 19 files changed, 381 insertions(+), 376 deletions(-) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index e8956e2dae..2defd9c7db 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -15,14 +15,14 @@ interface InternalPoolsRouter { fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) - fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) + fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) fun openRemoveLiquidityConfirmScreen( chainId: ChainId, ids: StringPair, - amountFrom: BigDecimal, - amountTo: BigDecimal, + amountBase: BigDecimal, + amountTarget: BigDecimal, firstAmountMin: BigDecimal, secondAmountMin: BigDecimal, desired: BigDecimal diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index b11eb6d4ed..1b40588c75 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -50,8 +50,8 @@ sealed interface LiquidityPoolsNavGraphRoute { class LiquidityAddConfirmScreen( val chainId: ChainId, val ids: StringPair, - val amountFrom: BigDecimal, - val amountTo: BigDecimal, + val amountBase: BigDecimal, + val amountTarget: BigDecimal, val apy: String ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -71,8 +71,8 @@ sealed interface LiquidityPoolsNavGraphRoute { class LiquidityRemoveConfirmScreen( val chainId: ChainId, val ids: StringPair, - val amountFrom: BigDecimal, - val amountTo: BigDecimal, + val amountBase: BigDecimal, + val amountTarget: BigDecimal, val firstAmountMin: BigDecimal, val secondAmountMin: BigDecimal, val desired: BigDecimal, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 05d3d56366..827e0c7730 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -54,8 +54,8 @@ class InternalPoolsRouterImpl( mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(chainId, ids)) } - override fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountFrom: BigDecimal, amountTo: BigDecimal, apy: String) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(chainId, ids, amountFrom, amountTo, apy)) + override fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(chainId, ids, amountBase, amountTarget, apy)) } override fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) { @@ -65,13 +65,13 @@ class InternalPoolsRouterImpl( override fun openRemoveLiquidityConfirmScreen( chainId: ChainId, ids: StringPair, - amountFrom: BigDecimal, - amountTo: BigDecimal, + amountBase: BigDecimal, + amountTarget: BigDecimal, firstAmountMin: BigDecimal, secondAmountMin: BigDecimal, desired: BigDecimal ) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountFrom, amountTo, firstAmountMin, secondAmountMin, desired)) + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired)) } override fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 2acc9971eb..46b14f1a7c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -82,7 +82,7 @@ fun AllPoolsScreen( ) { PoolGroupHeader( title = stringResource(id = R.string.pl_your_pools), - onMoreClick = { callback.onMoreClick(true) } // todo restore .takeIf { state.hasExtraUserPools } + onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } ) state.userPools.forEach { pool -> BasicPoolListItem( @@ -124,7 +124,7 @@ fun AllPoolsScreen( } @Composable -fun ShimmerPoolList() { +fun ShimmerPoolList(size: Int = 10) { BackgroundCorneredWithBorder( modifier = Modifier .padding(horizontal = 16.dp) @@ -137,7 +137,7 @@ fun ShimmerPoolList() { title = stringResource(id = R.string.pl_all_pools), onMoreClick = null ) - repeat(10) { + repeat(size) { BasicPoolShimmerItem(modifier = Modifier.padding(vertical = Dimens.x1)) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 6ef22f3f03..a569642a68 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -26,7 +26,6 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateAddLiquidityUseCase import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.data.LiquidityData import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository @@ -65,26 +64,42 @@ class LiquidityAddPresenter @Inject constructor( private val resourceManager: ResourceManager, private val validateAddLiquidityUseCase: ValidateAddLiquidityUseCase, ) : LiquidityAddCallbacks { - private val enteredFromAmountFlow = MutableStateFlow(BigDecimal.ZERO) - private val enteredToAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredBaseAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredTargetAmountFlow = MutableStateFlow(BigDecimal.ZERO) - private var amountFrom: BigDecimal = BigDecimal.ZERO - private var amountTo: BigDecimal = BigDecimal.ZERO + private var amountBase: BigDecimal = BigDecimal.ZERO + private var amountTarget: BigDecimal = BigDecimal.ZERO - private val isFromAmountFocused = MutableStateFlow(false) - private val isToAmountFocused = MutableStateFlow(false) + private val isBaseAmountFocused = MutableStateFlow(false) + private val isTargetAmountFocused = MutableStateFlow(false) private var desired: WithDesired = WithDesired.INPUT private val _stateSlippage = MutableStateFlow(0.5) val stateSlippage = _stateSlippage.asStateFlow() - private var liquidityData: LiquidityData = LiquidityData() - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() + .onEach { + resetState() + } .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + private fun resetState() { + amountTarget = BigDecimal.ZERO + amountBase = BigDecimal.ZERO + stateFlow.value = stateFlow.value.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ), + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ) + ) + } + @OptIn(ExperimentalCoroutinesApi::class) val assetsInPoolFlow = screenArgsFlow.distinctUntilChanged().flatMapLatest { screenArgs -> val ids = screenArgs.ids @@ -110,63 +125,54 @@ class LiquidityAddPresenter @Inject constructor( assetsFlow }.distinctUntilChanged() - val tokensInPoolFlow = assetsInPoolFlow.map { + private val tokensInPoolFlow = assetsInPoolFlow.map { it.first.asset.token.configuration to it.second.asset.token.configuration }.distinctUntilChanged() -// val userPoolDataFlow = flowOf { getPoolDataDto() } - - init { - } - @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { println("!!! AddPresenter subscribeState") - screenArgsFlow.flatMapLatest { - val (tokenFromId, tokenToId) = it.ids - println("!!! AddPresenter screenArgsFlow = $it") - poolsInteractor.getPoolData(it.chainId, tokenFromId, tokenToId).onEach { + screenArgsFlow.flatMapLatest { screenargs -> + println("!!! AddPresenter screenArgsFlow = $screenargs") + poolsInteractor.getPoolData( + chainId = screenargs.chainId, + baseTokenId = screenargs.ids.first, + targetTokenId = screenargs.ids.second + ).onEach { stateFlow.value = stateFlow.value.copy( apy = it.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" } ) } }.launchIn(coroutineScope) -// userPoolDataFlow.onEach { pool -> -// val apy = pool?.reservesAccount?.let { poolsInteractor.getPoolStrategicBonusAPY(it) } -// stateFlow.value = stateFlow.value.copy( -// apy = apy?.toBigDecimal()?.formatPercent()?.let { "$it%" } -// ) -// }.launchIn(coroutineScope) + assetsInPoolFlow.onEach { (assetBase, assetTarget) -> + val totalBaseCrypto = assetBase.asset.total?.formatCrypto(assetBase.asset.token.configuration.symbol).orEmpty() + val totalBaseFiat = assetBase.asset.fiatAmount?.formatFiat(assetBase.asset.token.fiatSymbol) + val argsBase = totalBaseCrypto + totalBaseFiat?.let { " ($it)" }.orEmpty() + val totalBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) - assetsInPoolFlow.onEach { (assetFrom, assetTo) -> - val totalFromCrypto = assetFrom.asset.total?.formatCrypto(assetFrom.asset.token.configuration.symbol).orEmpty() - val totalFromFiat = assetFrom.asset.fiatAmount?.formatFiat(assetFrom.asset.token.fiatSymbol) - val argsFrom = totalFromCrypto + totalFromFiat?.let { " ($it)" } - val totalFromBalance = resourceManager.getString(R.string.common_available_format, argsFrom) - - val totalToCrypto = assetTo.asset.total?.formatCrypto(assetTo.asset.token.configuration.symbol).orEmpty() - val totalToFiat = assetTo.asset.fiatAmount?.formatFiat(assetTo.asset.token.fiatSymbol) - val argsTo = totalToCrypto + totalToFiat?.let { " ($it)" } - val totalToBalance = resourceManager.getString(R.string.common_available_format, argsTo) + val totalTargetCrypto = assetTarget.asset.total?.formatCrypto(assetTarget.asset.token.configuration.symbol).orEmpty() + val totalTargetFiat = assetTarget.asset.fiatAmount?.formatFiat(assetTarget.asset.token.fiatSymbol) + val argsTarget = totalTargetCrypto + totalTargetFiat?.let { " ($it)" }.orEmpty() + val totalTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( - tokenName = assetFrom.asset.token.configuration.symbol, - tokenImage = assetFrom.asset.token.configuration.iconUrl, - totalBalance = totalFromBalance, + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( + tokenName = assetBase.asset.token.configuration.symbol, + tokenImage = assetBase.asset.token.configuration.iconUrl, + totalBalance = totalBaseBalance, ), - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( - tokenName = assetTo.asset.token.configuration.symbol, - tokenImage = assetTo.asset.token.configuration.iconUrl, - totalBalance = totalToBalance, + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( + tokenName = assetTarget.asset.token.configuration.symbol, + tokenImage = assetTarget.asset.token.configuration.iconUrl, + totalBalance = totalTargetBalance, ) ) }.launchIn(coroutineScope) - enteredFromAmountFlow.onEach { + enteredBaseAmountFlow.onEach { stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatSymbol), tokenAmount = it, ) @@ -174,14 +180,14 @@ class LiquidityAddPresenter @Inject constructor( } .debounce(900) .onEach { amount -> - amountFrom = amount + amountBase = amount desired = WithDesired.INPUT updateAmounts() }.launchIn(coroutineScope) - enteredToAmountFlow.onEach { + enteredTargetAmountFlow.onEach { stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatSymbol), tokenAmount = it ), @@ -189,22 +195,22 @@ class LiquidityAddPresenter @Inject constructor( } .debounce(900) .onEach { amount -> - amountTo = amount + amountTarget = amount desired = WithDesired.OUTPUT updateAmounts() }.launchIn(coroutineScope) - isFromAmountFocused.onEach { + isBaseAmountFocused.onEach { stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( isFocused = it ), ) }.launchIn(coroutineScope) - isToAmountFocused.onEach { + isTargetAmountFocused.onEach { stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( isFocused = it ), ) @@ -255,24 +261,24 @@ class LiquidityAddPresenter @Inject constructor( } if (desired == WithDesired.INPUT) { - val tokenTo = assetsInPoolFlow.firstOrNull()?.second?.asset?.token + val tokenTarget = assetsInPoolFlow.firstOrNull()?.second?.asset?.token stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( tokenAmount = scaledTargetAmount, - fiatAmount = scaledTargetAmount.applyFiatRate(tokenTo?.fiatRate)?.formatFiat(tokenTo?.fiatSymbol), + fiatAmount = scaledTargetAmount.applyFiatRate(tokenTarget?.fiatRate)?.formatFiat(tokenTarget?.fiatSymbol), ) ) - amountTo = scaledTargetAmount + amountTarget = scaledTargetAmount } else { - val tokenFrom = assetsInPoolFlow.firstOrNull()?.first?.asset?.token + val tokenBase = assetsInPoolFlow.firstOrNull()?.first?.asset?.token stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( tokenAmount = scaledTargetAmount, - fiatAmount = scaledTargetAmount.applyFiatRate(tokenFrom?.fiatRate)?.formatFiat(tokenFrom?.fiatSymbol), + fiatAmount = scaledTargetAmount.applyFiatRate(tokenBase?.fiatRate)?.formatFiat(tokenBase?.fiatSymbol), ) ) - amountFrom = scaledTargetAmount + amountBase = scaledTargetAmount } } @@ -280,8 +286,8 @@ class LiquidityAddPresenter @Inject constructor( } private fun updateButtonState() { - val isButtonEnabled = amountTo.moreThanZero() && amountTo.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null - println("!!! updateButtonState: $amountTo; $amountTo; ${stateFlow.value.feeInfo.feeAmount}") + val isButtonEnabled = amountTarget.moreThanZero() && amountTarget.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null + println("!!! updateButtonState: $amountTarget; $amountTarget; ${stateFlow.value.feeInfo.feeAmount}") println("!!! updateButtonState: isButtonEnabled = $isButtonEnabled") stateFlow.value = stateFlow.value.copy( buttonEnabled = isButtonEnabled @@ -292,15 +298,15 @@ class LiquidityAddPresenter @Inject constructor( private suspend fun calculateAmount(): BigDecimal? { val assets = assetsInPoolFlow.firstOrNull() - val baseAmount = if (desired == WithDesired.INPUT) enteredFromAmountFlow.value else enteredToAmountFlow.value - val targetAmount = if (desired == WithDesired.INPUT) enteredToAmountFlow.value else enteredFromAmountFlow.value - -// val liquidity2 = userPoolDataFlow.firstOrNull() + val baseAmount = if (desired == WithDesired.INPUT) enteredBaseAmountFlow.value else enteredTargetAmountFlow.value + val targetAmount = if (desired == WithDesired.INPUT) enteredTargetAmountFlow.value else enteredBaseAmountFlow.value val liquidity = screenArgsFlow.flatMapLatest { screenArgs -> - val chainId = screenArgs.chainId - val (tokenFromId, tokenToId) = screenArgs.ids - poolsInteractor.getPoolData(chainId, tokenFromId, tokenToId) + poolsInteractor.getPoolData( + chainId = screenArgs.chainId, + baseTokenId = screenArgs.ids.first, + targetTokenId = screenArgs.ids.second + ) }.firstOrNull() return assets?.let { (baseAsset, targetAsset) -> @@ -333,8 +339,8 @@ class LiquidityAddPresenter @Inject constructor( } val networkFeeFlow = combine( - enteredFromAmountFlow, - enteredToAmountFlow, + enteredBaseAmountFlow, + enteredTargetAmountFlow, tokensInPoolFlow, stateSlippage, isPoolPairEnabled @@ -421,12 +427,12 @@ class LiquidityAddPresenter @Inject constructor( val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch val validationResult = validateAddLiquidityUseCase( - assetFrom = poolAssets.first, - assetTo = poolAssets.second, + assetBase = poolAssets.first, + assetTarget = poolAssets.second, utilityAssetId = utilityAssetId, utilityAmount = utilityAmount.orZero(), - amountFrom = amountFrom, - amountTo = amountTo, + amountBase = amountBase, + amountTarget = amountTarget, feeAmount = feeAmount, ) @@ -442,7 +448,7 @@ class LiquidityAddPresenter @Inject constructor( } val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openAddLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo, stateFlow.value.apy.orEmpty()) + internalPoolsRouter.openAddLiquidityConfirmScreen(chainId, ids, amountBase, amountTarget, stateFlow.value.apy.orEmpty()) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { @@ -452,29 +458,29 @@ class LiquidityAddPresenter @Inject constructor( } } - override fun onAddFromAmountChange(amount: BigDecimal) { - enteredFromAmountFlow.value = amount - amountFrom = amount + override fun onAddBaseAmountChange(amount: BigDecimal) { + enteredBaseAmountFlow.value = amount + amountBase = amount updateButtonState() } - override fun onAddToAmountChange(amount: BigDecimal) { - enteredToAmountFlow.value = amount - amountTo = amount + override fun onAddTargetAmountChange(amount: BigDecimal) { + enteredTargetAmountFlow.value = amount + amountTarget = amount updateButtonState() } - override fun onAddFromAmountFocusChange(isFocused: Boolean) { - isFromAmountFocused.value = isFocused + override fun onAddBaseAmountFocusChange(isFocused: Boolean) { + isBaseAmountFocused.value = isFocused if (desired != WithDesired.INPUT) { desired = WithDesired.INPUT } } - override fun onAddToAmountFocusChange(isFocused: Boolean) { - isToAmountFocused.value = isFocused + override fun onAddTargetAmountFocusChange(isFocused: Boolean) { + isTargetAmountFocused.value = isFocused if (desired != WithDesired.OUTPUT) { desired = WithDesired.OUTPUT } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index 65955efac9..c1886e475a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -37,13 +37,12 @@ import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityAddState( - val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, - val toAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val baseAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val targetAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, val slippage: String = "0.5%", val apy: String? = null, val feeInfo: FeeInfoViewState = FeeInfoViewState.default, @@ -55,13 +54,13 @@ interface LiquidityAddCallbacks { fun onAddReviewClick() - fun onAddFromAmountChange(amount: BigDecimal) + fun onAddBaseAmountChange(amount: BigDecimal) - fun onAddFromAmountFocusChange(isFocused: Boolean) + fun onAddBaseAmountFocusChange(isFocused: Boolean) - fun onAddToAmountChange(amount: BigDecimal) + fun onAddTargetAmountChange(amount: BigDecimal) - fun onAddToAmountFocusChange(isFocused: Boolean) + fun onAddTargetAmountFocusChange(isFocused: Boolean) fun onAddTableItemClick(itemId: Int) } @@ -99,20 +98,20 @@ fun LiquidityAddScreen( ) { Column { AmountInput( - state = state.fromAmountInputViewState, + state = state.baseAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onAddFromAmountChange, - onInputFocusChange = callbacks::onAddFromAmountFocusChange, + onInput = callbacks::onAddBaseAmountChange, + onInputFocusChange = callbacks::onAddBaseAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) MarginVertical(margin = 8.dp) AmountInput( - state = state.toAmountInputViewState, + state = state.targetAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onAddToAmountChange, - onInputFocusChange = callbacks::onAddToAmountFocusChange, + onInput = callbacks::onAddTargetAmountChange, + onInputFocusChange = callbacks::onAddTargetAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) } @@ -191,18 +190,18 @@ fun LiquidityAddScreen( private fun PreviewLiquidityAddScreen() { LiquidityAddScreen( state = LiquidityAddState( - fromAmountInputViewState = AmountInputViewState.defaultObj, - toAmountInputViewState = AmountInputViewState.defaultObj, + baseAmountInputViewState = AmountInputViewState.defaultObj, + targetAmountInputViewState = AmountInputViewState.defaultObj, apy = "23.3%", feeInfo = FeeInfoViewState.default, slippage = "0.5%" ), callbacks = object : LiquidityAddCallbacks { override fun onAddReviewClick() {} - override fun onAddFromAmountChange(amount: BigDecimal) {} - override fun onAddFromAmountFocusChange(isFocused: Boolean) {} - override fun onAddToAmountChange(amount: BigDecimal) {} - override fun onAddToAmountFocusChange(isFocused: Boolean) {} + override fun onAddBaseAmountChange(amount: BigDecimal) {} + override fun onAddBaseAmountFocusChange(isFocused: Boolean) {} + override fun onAddTargetAmountChange(amount: BigDecimal) {} + override fun onAddTargetAmountFocusChange(isFocused: Boolean) {} override fun onAddTableItemClick(itemId: Int) {} }, ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 52d360de4b..869d515c5d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -52,7 +52,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( private val _stateSlippage = MutableStateFlow(0.5) val stateSlippage = _stateSlippage.asStateFlow() - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) @@ -78,17 +77,16 @@ class LiquidityAddConfirmPresenter @Inject constructor( assetsFlow } - val tokensInPoolFlow = assetsInPoolFlow.map { + private val tokensInPoolFlow = assetsInPoolFlow.map { it.first.asset.token to it.second.asset.token }.distinctUntilChanged() - val isPoolPairEnabled = + private val isPoolPairEnabled = screenArgsFlow.map { screenArgs -> - val (tokenFromId, tokenToId) = screenArgs.ids poolsInteractor.isPairEnabled( - screenArgs.chainId, - tokenFromId, - tokenToId + chainId = screenArgs.chainId, + baseTokenId = screenArgs.ids.first, + targetTokenId = screenArgs.ids.second ) } @@ -104,14 +102,14 @@ class LiquidityAddConfirmPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { - combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetFrom, assetTo) -> + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> stateFlow.value = stateFlow.value.copy( - assetFrom = assetFrom.configuration, - assetTo = assetTo.configuration, - baseAmount = screenArgs.amountFrom.formatCrypto(assetFrom.configuration.symbol), - baseFiat = screenArgs.amountFrom.applyFiatRate(assetFrom.fiatRate)?.formatFiat(assetFrom.fiatSymbol).orEmpty(), - targetAmount = screenArgs.amountTo.formatCrypto(assetTo.configuration.symbol), - targetFiat = screenArgs.amountTo.applyFiatRate(assetTo.fiatRate)?.formatFiat(assetTo.fiatSymbol).orEmpty(), + assetBase = assetBase.configuration, + assetTarget = assetTarget.configuration, + baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), + baseFiat = screenArgs.amountBase.applyFiatRate(assetBase.fiatRate)?.formatFiat(assetBase.fiatSymbol).orEmpty(), + targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), + targetFiat = screenArgs.amountTarget.applyFiatRate(assetTarget.fiatRate)?.formatFiat(assetTarget.fiatSymbol).orEmpty(), apy = screenArgs.apy ) }.launchIn(coroutineScope) @@ -139,10 +137,10 @@ class LiquidityAddConfirmPresenter @Inject constructor( ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> val networkFee = getLiquidityNetworkFee( - tokenFrom = baseAsset.configuration, - tokenTo = targetAsset.configuration, - tokenFromAmount = screenArgs.amountFrom, - tokenToAmount = screenArgs.amountTo, + tokenBase = baseAsset.configuration, + tokenTarget = targetAsset.configuration, + tokenBaseAmount = screenArgs.amountBase, + tokenTargetAmount = screenArgs.amountTarget, pairEnabled = pairEnabled, pairPresented = true, //pairPresented, slippageTolerance = slippage @@ -173,10 +171,10 @@ class LiquidityAddConfirmPresenter @Inject constructor( } private suspend fun getLiquidityNetworkFee( - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -185,15 +183,15 @@ class LiquidityAddConfirmPresenter @Inject constructor( val soraChain = walletInteractor.getChain(chainId) val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val result = poolsInteractor.calcAddLiquidityNetworkFee( - chainId, - user, - tokenFrom, - tokenTo, - tokenFromAmount, - tokenToAmount, - pairEnabled, - pairPresented, - slippageTolerance, + chainId = chainId, + address = user, + tokenBase = tokenBase, + tokenTarget = tokenTarget, + tokenBaseAmount = tokenBaseAmount, + tokenTargetAmount = tokenTargetAmount, + pairEnabled = pairEnabled, + pairPresented = pairPresented, + slippageTolerance = slippageTolerance, ) return result ?: BigDecimal.ZERO } @@ -204,8 +202,8 @@ class LiquidityAddConfirmPresenter @Inject constructor( val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch val tokenBase = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch val tokenTarget = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch - val amountBase = screenArgsFlow.firstOrNull()?.amountFrom.orZero() - val amountTarget = screenArgsFlow.firstOrNull()?.amountTo.orZero() + val amountBase = screenArgsFlow.firstOrNull()?.amountBase.orZero() + val amountTarget = screenArgsFlow.firstOrNull()?.amountTarget.orZero() val pairEnabled = isPoolPairEnabled.firstOrNull() ?: true var result = "" try { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 629ff78b27..f077910e6f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -44,13 +44,12 @@ import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white50 import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityAddConfirmState( - val assetFrom: Asset? = null, - val assetTo: Asset? = null, + val assetBase: Asset? = null, + val assetTarget: Asset? = null, val baseAmount: String = "", val baseFiat: String = "", val targetAmount: String = "", @@ -90,7 +89,7 @@ fun LiquidityAddConfirmScreen( Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { AsyncImage( - model = getImageRequest(LocalContext.current, state.assetFrom?.iconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetBase?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) @@ -98,7 +97,7 @@ fun LiquidityAddConfirmScreen( .zIndex(1f) ) AsyncImage( - model = getImageRequest(LocalContext.current, state.assetTo?.iconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetTarget?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) @@ -115,7 +114,7 @@ fun LiquidityAddConfirmScreen( horizontalArrangement = Arrangement.Center ) { Text( - text = state.assetFrom?.symbol?.uppercase().orEmpty(), + text = state.assetBase?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -133,7 +132,7 @@ fun LiquidityAddConfirmScreen( MarginHorizontal(margin = 8.dp) Text( - text = state.assetTo?.symbol?.uppercase().orEmpty(), + text = state.assetTarget?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -185,14 +184,14 @@ fun LiquidityAddConfirmScreen( ) InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetFrom?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.pl_your_pooled_format, state.assetBase?.symbol?.uppercase().orEmpty()), value = state.baseAmount, additionalValue = state.baseFiat ) ) InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetTo?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.pl_your_pooled_format, state.assetTarget?.symbol?.uppercase().orEmpty()), value = state.targetAmount, additionalValue = state.targetFiat ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index e17dbb3d1c..700680e01f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -13,7 +13,6 @@ import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat -import jp.co.soramitsu.common.utils.isNotZero import jp.co.soramitsu.common.utils.moreThanZero import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.requireValue @@ -23,7 +22,6 @@ import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateRemoveLiquidityUseCase import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute @@ -68,14 +66,14 @@ class LiquidityRemovePresenter @Inject constructor( private val resourceManager: ResourceManager, private val validateRemoveLiquidityUseCase: ValidateRemoveLiquidityUseCase, ) : LiquidityRemoveCallbacks { - private val enteredFromAmountFlow = MutableStateFlow(BigDecimal.ZERO) - private val enteredToAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredBaseAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val enteredTargetAmountFlow = MutableStateFlow(BigDecimal.ZERO) - private var amountFrom: BigDecimal = BigDecimal.ZERO - private var amountTo: BigDecimal = BigDecimal.ZERO + private var amountBase: BigDecimal = BigDecimal.ZERO + private var amountTarget: BigDecimal = BigDecimal.ZERO - private val isFromAmountFocused = MutableStateFlow(false) - private val isToAmountFocused = MutableStateFlow(false) + private val isBaseAmountFocused = MutableStateFlow(false) + private val isTargetAmountFocused = MutableStateFlow(false) private var poolInFarming = false private var poolDataUsable: CommonUserPoolData? = null @@ -85,8 +83,26 @@ class LiquidityRemovePresenter @Inject constructor( private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() + .onEach { + resetState() + } .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) + private fun resetState() { + amountTarget = BigDecimal.ZERO + amountBase = BigDecimal.ZERO + stateFlow.value = stateFlow.value.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ), + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ) + ) + } + @OptIn(ExperimentalCoroutinesApi::class) val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids @@ -112,14 +128,17 @@ class LiquidityRemovePresenter @Inject constructor( assetsFlow }.distinctUntilChanged() - val tokensInPoolFlow = assetsInPoolFlow.map { + private val tokensInPoolFlow = assetsInPoolFlow.map { it.first.asset.token.configuration to it.second.asset.token.configuration }.distinctUntilChanged() @OptIn(ExperimentalCoroutinesApi::class) val poolDataFlow = screenArgsFlow.flatMapLatest { - val (tokenFromId, tokenToId) = it.ids - poolsInteractor.getPoolData(it.chainId, tokenFromId, tokenToId) + poolsInteractor.getPoolData( + chainId = it.chainId, + baseTokenId = it.ids.first, + targetTokenId = it.ids.second + ) } init { @@ -191,13 +210,13 @@ class LiquidityRemovePresenter @Inject constructor( println("!!! poolDataFlow collectLatest poolDataLocal = $poolDataLocal") poolDataUsable = poolDataLocal - amountFrom = + amountBase = if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.basePooled, percent, poolDataLocal.basic.baseToken.token.configuration.precision, ) else BigDecimal.ZERO - amountTo = + amountTarget = if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.targetPooled, percent, @@ -217,26 +236,26 @@ class LiquidityRemovePresenter @Inject constructor( val baseToken = it.basic.baseToken.token val targetToken = it.basic.targetToken?.token - val pooledFromCrypto = it.user?.basePooled?.formatCrypto(baseToken.configuration.symbol).orEmpty() - val pooledFromFiat = it.user?.basePooled?.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol) - val argsFrom = pooledFromCrypto + pooledFromFiat?.let { " ($it)" } - val pooledFromBalance = resourceManager.getString(R.string.common_available_format, argsFrom) + val pooledBaseCrypto = it.user?.basePooled?.formatCrypto(baseToken.configuration.symbol).orEmpty() + val pooledBaseFiat = it.user?.basePooled?.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol) + val argsBase = pooledBaseCrypto + pooledBaseFiat?.let { " ($it)" }.orEmpty() + val pooledBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) - val pooledToCrypto = it.user?.targetPooled?.formatCrypto(targetToken?.configuration?.symbol).orEmpty() - val pooledToFiat = it.user?.targetPooled?.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol) - val argsTo = pooledToCrypto + pooledToFiat?.let { " ($it)" } - val pooledToBalance = resourceManager.getString(R.string.common_available_format, argsTo) + val pooledTargetCrypto = it.user?.targetPooled?.formatCrypto(targetToken?.configuration?.symbol).orEmpty() + val pooledTargetFiat = it.user?.targetPooled?.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol) + val argsTarget = pooledTargetCrypto + pooledTargetFiat?.let { " ($it)" }.orEmpty() + val pooledTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( tokenName = baseToken.configuration.symbol, tokenImage = baseToken.configuration.iconUrl, - totalBalance = pooledFromBalance, + totalBalance = pooledBaseBalance, ), - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( tokenName = targetToken?.configuration?.symbol, tokenImage = targetToken?.configuration?.iconUrl, - totalBalance = pooledToBalance, + totalBalance = pooledTargetBalance, ) ) }.launchIn(coroutineScope) @@ -248,10 +267,10 @@ class LiquidityRemovePresenter @Inject constructor( ) }.launchIn(coroutineScope) - enteredFromAmountFlow.onEach { + enteredBaseAmountFlow.onEach { val baseToken = assetsInPoolFlow.firstOrNull()?.first?.asset?.token stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( fiatAmount = it.applyFiatRate(baseToken?.fiatRate)?.formatFiat(baseToken?.fiatSymbol), tokenAmount = it, ) @@ -260,17 +279,17 @@ class LiquidityRemovePresenter @Inject constructor( .debounce(900) .onEach { amount -> poolDataUsable?.let { - amountFrom = if (it.user.basePooled <= amount) amount else it.user.basePooled + amountBase = if (it.user.basePooled <= amount) amount else it.user.basePooled val precisionTo = poolDataFlow.firstOrNull()?.basic?.targetToken?.token?.configuration?.precision - amountTo = PolkaswapFormulas.calculateOneAmountFromAnother( - amountFrom, + amountTarget = PolkaswapFormulas.calculateOneAmountFromAnother( + amountBase, it.user.basePooled, it.user.targetPooled, precisionTo ) percent = PolkaswapFormulas.calculateShareOfPoolFromAmount( - amountFrom, + amountBase, it.user.basePooled, ) } @@ -280,12 +299,12 @@ class LiquidityRemovePresenter @Inject constructor( } }.launchIn(coroutineScope) - enteredToAmountFlow.onEach { + enteredTargetAmountFlow.onEach { println("!!! enteredToAmountFlow.onEach = $it") val targetToken = assetsInPoolFlow.firstOrNull()?.second?.asset?.token stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( fiatAmount = it.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol), tokenAmount = it ), @@ -295,17 +314,17 @@ class LiquidityRemovePresenter @Inject constructor( .onEach { amount -> println("!!! enteredToAmountFlow.onEach debounced = $amount") poolDataUsable?.let { - amountTo = if (amount <= it.user.targetPooled) amount else it.user.targetPooled + amountTarget = if (amount <= it.user.targetPooled) amount else it.user.targetPooled - val precisionFrom = poolDataFlow.firstOrNull()?.basic?.baseToken?.token?.configuration?.precision - amountFrom = PolkaswapFormulas.calculateOneAmountFromAnother( - amountTo, + val precisionBase = poolDataFlow.firstOrNull()?.basic?.baseToken?.token?.configuration?.precision + amountBase = PolkaswapFormulas.calculateOneAmountFromAnother( + amountTarget, it.user.targetPooled, it.user.basePooled, - precisionFrom + precisionBase ) percent = PolkaswapFormulas.calculateShareOfPoolFromAmount( - amountFrom, + amountBase, it.user.basePooled, ) } @@ -316,17 +335,17 @@ class LiquidityRemovePresenter @Inject constructor( } }.launchIn(coroutineScope) - isFromAmountFocused.onEach { + isBaseAmountFocused.onEach { stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( isFocused = it ), ) }.launchIn(coroutineScope) - isToAmountFocused.onEach { + isTargetAmountFocused.onEach { stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( isFocused = it ), ) @@ -348,38 +367,38 @@ class LiquidityRemovePresenter @Inject constructor( } private suspend fun updateAmounts() { - assetsInPoolFlow.firstOrNull()?.let { (assetFrom, assetTo) -> - if (amountFrom.compareTo(stateFlow.value.fromAmountInputViewState.tokenAmount) != 0) { - println("!!! updateAmounts amountFrom to $amountFrom") - - val scaledAmountFrom = when { - amountFrom.isZero() -> BigDecimal.ZERO - else -> amountFrom.setScale( - min(MAX_DECIMALS_8, amountFrom.scale()), + assetsInPoolFlow.firstOrNull()?.let { (assetBase, assetTarget) -> + if (amountBase.compareTo(stateFlow.value.baseAmountInputViewState.tokenAmount) != 0) { + println("!!! updateAmounts amountFrom to $amountBase") + + val scaledAmountBase = when { + amountBase.isZero() -> BigDecimal.ZERO + else -> amountBase.setScale( + min(MAX_DECIMALS_8, amountBase.scale()), RoundingMode.DOWN ) } stateFlow.value = stateFlow.value.copy( - fromAmountInputViewState = stateFlow.value.fromAmountInputViewState.copy( - tokenAmount = scaledAmountFrom, - fiatAmount = amountFrom.applyFiatRate(assetFrom.asset.token.fiatRate)?.formatFiat(assetFrom.asset.token.fiatSymbol), + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( + tokenAmount = scaledAmountBase, + fiatAmount = amountBase.applyFiatRate(assetBase.asset.token.fiatRate)?.formatFiat(assetBase.asset.token.fiatSymbol), ) ) } - if (amountTo.compareTo(stateFlow.value.toAmountInputViewState.tokenAmount) != 0) { - println("!!! updateAmounts amountTo to $amountTo") - val scaledAmountTo = when { - amountTo.isZero() -> BigDecimal.ZERO - else -> amountTo.setScale( - min(MAX_DECIMALS_8, amountTo.scale()), + if (amountTarget.compareTo(stateFlow.value.targetAmountInputViewState.tokenAmount) != 0) { + println("!!! updateAmounts amountTo to $amountTarget") + val scaledAmountTarget = when { + amountTarget.isZero() -> BigDecimal.ZERO + else -> amountTarget.setScale( + min(MAX_DECIMALS_8, amountTarget.scale()), RoundingMode.DOWN ) } stateFlow.value = stateFlow.value.copy( - toAmountInputViewState = stateFlow.value.toAmountInputViewState.copy( - tokenAmount = scaledAmountTo, - fiatAmount = amountTo.applyFiatRate(assetTo.asset.token.fiatRate)?.formatFiat(assetTo.asset.token.fiatSymbol), + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( + tokenAmount = scaledAmountTarget, + fiatAmount = amountTarget.applyFiatRate(assetTarget.asset.token.fiatRate)?.formatFiat(assetTarget.asset.token.fiatSymbol), ) ) } @@ -389,7 +408,7 @@ class LiquidityRemovePresenter @Inject constructor( } private fun updateButtonState() { - val isButtonEnabled = amountTo.moreThanZero() && amountTo.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null + val isButtonEnabled = amountTarget.moreThanZero() && amountTarget.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null stateFlow.value = stateFlow.value.copy( buttonEnabled = isButtonEnabled ) @@ -397,8 +416,8 @@ class LiquidityRemovePresenter @Inject constructor( private val networkFeeFlow = tokensInPoolFlow.map { (baseAsset, targetAsset) -> val networkFee = getRemoveLiquidityNetworkFee( - tokenFrom = baseAsset, - tokenTo = targetAsset, + tokenBase = baseAsset, + tokenTarget = targetAsset, ) println("!!!! RemoveLiquidity FeeFlow emit $networkFee") networkFee @@ -434,15 +453,15 @@ class LiquidityRemovePresenter @Inject constructor( } private suspend fun getRemoveLiquidityNetworkFee( - tokenFrom: Asset, - tokenTo: Asset, + tokenBase: Asset, + tokenTarget: Asset, ): BigDecimal { val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO val result = poolsInteractor.calcRemoveLiquidityNetworkFee( chainId, - tokenFrom, - tokenTo, + tokenBase, + tokenTarget, ) return result ?: BigDecimal.ZERO } @@ -459,8 +478,6 @@ class LiquidityRemovePresenter @Inject constructor( coroutinesStore.uiScope.launch { val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch -// val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) -// val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total val utilityAmount = utilityAssetFlow.firstOrNull()?.transferable ?: return@launch val feeAmount = networkFeeFlow.firstOrNull().orZero() @@ -473,8 +490,8 @@ class LiquidityRemovePresenter @Inject constructor( utilityAmount = utilityAmount.orZero(), userBasePooled = userBasePooled, userTargetPooled = userTargetPooled, - amountFrom = amountFrom, - amountTo = amountTo, + amountBase = amountBase, + amountTarget = amountTarget, feeAmount = feeAmount, ) @@ -493,12 +510,12 @@ class LiquidityRemovePresenter @Inject constructor( val firstAmountMin = PolkaswapFormulas.calculateMinAmount( - amountFrom, + amountBase, slippage ) val secondAmountMin = PolkaswapFormulas.calculateMinAmount( - amountTo, + amountTarget, slippage ) val desired = @@ -514,7 +531,7 @@ class LiquidityRemovePresenter @Inject constructor( val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountFrom, amountTo, firstAmountMin, secondAmountMin, desired) + internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { @@ -524,28 +541,24 @@ class LiquidityRemovePresenter @Inject constructor( } } - override fun onRemoveFromAmountChange(amount: BigDecimal) { - println("!!! onRemoveFromAmountChange(amount = $amount") - enteredFromAmountFlow.value = amount -// amountFrom = amount - + override fun onRemoveBaseAmountChange(amount: BigDecimal) { + println("!!! onRemoveBaseAmountChange(amount = $amount") + enteredBaseAmountFlow.value = amount updateButtonState() } - override fun onRemoveToAmountChange(amount: BigDecimal) { + override fun onRemoveTargetAmountChange(amount: BigDecimal) { println("!!! onRemoveToAmountChange(amount = $amount") - enteredToAmountFlow.value = amount -// amountTo = amount - + enteredTargetAmountFlow.value = amount updateButtonState() } - override fun onRemoveFromAmountFocusChange(isFocused: Boolean) { - isFromAmountFocused.value = isFocused + override fun onRemoveBaseAmountFocusChange(isFocused: Boolean) { + isBaseAmountFocused.value = isFocused } - override fun onRemoveToAmountFocusChange(isFocused: Boolean) { - isToAmountFocused.value = isFocused + override fun onRemoveTargetAmountFocusChange(isFocused: Boolean) { + isTargetAmountFocused.value = isFocused } override fun onRemoveItemClick(itemId: Int) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index 711622d516..0399d59eab 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -27,9 +27,7 @@ import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.compose.component.InfoTableItem -import jp.co.soramitsu.common.compose.component.InfoTableItemAsset import jp.co.soramitsu.common.compose.component.MarginVertical -import jp.co.soramitsu.common.compose.component.TitleIconValueState import jp.co.soramitsu.common.compose.component.TitleValueViewState import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.colorAccentDark @@ -37,12 +35,11 @@ import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityRemoveState( - val fromAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, - val toAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val baseAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, + val targetAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, val transferableAmount: String? = null, val transferableFiat: String? = null, val feeInfo: FeeInfoViewState = FeeInfoViewState.default, @@ -54,13 +51,13 @@ interface LiquidityRemoveCallbacks { fun onRemoveReviewClick() - fun onRemoveFromAmountChange(amount: BigDecimal) + fun onRemoveBaseAmountChange(amount: BigDecimal) - fun onRemoveFromAmountFocusChange(isFocused: Boolean) + fun onRemoveBaseAmountFocusChange(isFocused: Boolean) - fun onRemoveToAmountChange(amount: BigDecimal) + fun onRemoveTargetAmountChange(amount: BigDecimal) - fun onRemoveToAmountFocusChange(isFocused: Boolean) + fun onRemoveTargetAmountFocusChange(isFocused: Boolean) fun onRemoveItemClick(itemId: Int) } @@ -98,20 +95,20 @@ fun LiquidityRemoveScreen( ) { Column { AmountInput( - state = state.fromAmountInputViewState, + state = state.baseAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onRemoveFromAmountChange, - onInputFocusChange = callbacks::onRemoveFromAmountFocusChange, + onInput = callbacks::onRemoveBaseAmountChange, + onInputFocusChange = callbacks::onRemoveBaseAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) MarginVertical(margin = 8.dp) AmountInput( - state = state.toAmountInputViewState, + state = state.targetAmountInputViewState, borderColorFocused = colorAccentDark, - onInput = callbacks::onRemoveToAmountChange, - onInputFocusChange = callbacks::onRemoveToAmountFocusChange, + onInput = callbacks::onRemoveTargetAmountChange, + onInputFocusChange = callbacks::onRemoveTargetAmountFocusChange, onKeyboardDone = { keyboardController?.hide() } ) } @@ -177,16 +174,16 @@ fun LiquidityRemoveScreen( private fun PreviewLiquidityRemoveScreen() { LiquidityRemoveScreen( state = LiquidityRemoveState( - fromAmountInputViewState = AmountInputViewState.defaultObj, - toAmountInputViewState = AmountInputViewState.defaultObj, + baseAmountInputViewState = AmountInputViewState.defaultObj, + targetAmountInputViewState = AmountInputViewState.defaultObj, feeInfo = FeeInfoViewState.default, ), callbacks = object : LiquidityRemoveCallbacks { override fun onRemoveReviewClick() {} - override fun onRemoveFromAmountChange(amount: BigDecimal) {} - override fun onRemoveFromAmountFocusChange(isFocused: Boolean) {} - override fun onRemoveToAmountChange(amount: BigDecimal) {} - override fun onRemoveToAmountFocusChange(isFocused: Boolean) {} + override fun onRemoveBaseAmountChange(amount: BigDecimal) {} + override fun onRemoveBaseAmountFocusChange(isFocused: Boolean) {} + override fun onRemoveTargetAmountChange(amount: BigDecimal) {} + override fun onRemoveTargetAmountFocusChange(isFocused: Boolean) {} override fun onRemoveItemClick(itemId: Int) {} }, ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index ae4b14669b..ff36c2dc03 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -77,24 +77,19 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( assetsFlow } - val tokensInPoolFlow = assetsInPoolFlow.map { + private val tokensInPoolFlow = assetsInPoolFlow.map { it.first.asset.token to it.second.asset.token }.distinctUntilChanged() - val isPoolPairEnabled = + private val isPoolPairEnabled = screenArgsFlow.map { screenArgs -> - val (tokenFromId, tokenToId) = screenArgs.ids poolsInteractor.isPairEnabled( - screenArgs.chainId, - tokenFromId, - tokenToId + chainId = screenArgs.chainId, + baseTokenId = screenArgs.ids.first, + targetTokenId = screenArgs.ids.second ) } - - init { - - } private val stateFlow = MutableStateFlow(LiquidityRemoveConfirmState()) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { @@ -103,15 +98,14 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { - combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetFrom, assetTo) -> + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> stateFlow.value = stateFlow.value.copy( - assetFromIconUrl = assetFrom.configuration.iconUrl, - assetToIconUrl = assetTo.configuration.iconUrl, - baseAmount = screenArgs.amountFrom.formatCrypto(assetFrom.configuration.symbol), - baseFiat = screenArgs.amountFrom.applyFiatRate(assetFrom.fiatRate)?.formatFiat(assetFrom.fiatSymbol).orEmpty(), - targetAmount = screenArgs.amountTo.formatCrypto(assetTo.configuration.symbol), - targetFiat = screenArgs.amountTo.applyFiatRate(assetTo.fiatRate)?.formatFiat(assetTo.fiatSymbol).orEmpty(), -// apy = screenArgs.apy + assetBaseIconUrl = assetBase.configuration.iconUrl, + assetTargetIconUrl = assetTarget.configuration.iconUrl, + baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), + baseFiat = screenArgs.amountBase.applyFiatRate(assetBase.fiatRate)?.formatFiat(assetBase.fiatSymbol).orEmpty(), + targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), + targetFiat = screenArgs.amountTarget.applyFiatRate(assetTarget.fiatRate)?.formatFiat(assetTarget.fiatSymbol).orEmpty(), ) }.launchIn(coroutineScope) @@ -121,10 +115,9 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( buttonEnabled = it.feeAmount.isNullOrEmpty().not() ) }.launchIn(coroutineScope) - } - val networkFeeFlow = combine( + private val networkFeeFlow = combine( screenArgsFlow, tokensInPoolFlow, stateSlippage, @@ -132,12 +125,12 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> val networkFee = getLiquidityNetworkFee( - tokenFrom = baseAsset.configuration, - tokenTo = targetAsset.configuration, - tokenFromAmount = screenArgs.amountFrom, - tokenToAmount = screenArgs.amountTo, + tokenBase = baseAsset.configuration, + tokenTarget = targetAsset.configuration, + tokenBaseAmount = screenArgs.amountBase, + tokenTargetAmount = screenArgs.amountTarget, pairEnabled = pairEnabled, - pairPresented = true, //pairPresented, + pairPresented = true, slippageTolerance = slippage ) println("!!!! networkFeeFlow emit $networkFee") @@ -166,10 +159,10 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } private suspend fun getLiquidityNetworkFee( - tokenFrom: Asset, - tokenTo: Asset, - tokenFromAmount: BigDecimal, - tokenToAmount: BigDecimal, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, pairEnabled: Boolean, pairPresented: Boolean, slippageTolerance: Double @@ -180,10 +173,10 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( val result = poolsInteractor.calcAddLiquidityNetworkFee( chainId, user, - tokenFrom, - tokenTo, - tokenFromAmount, - tokenToAmount, + tokenBase, + tokenTarget, + tokenBaseAmount, + tokenTargetAmount, pairEnabled, pairPresented, slippageTolerance, @@ -200,21 +193,21 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( val networkFee = networkFeeFlow.firstOrNull() ?: return@launch val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - val tokenFrom = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch - val tokenTo = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch + val tokenBase = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch + val tokenTarget = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch var result = "" try { result = poolsInteractor.observeRemoveLiquidity( - chainId, - tokenFrom, - tokenTo, - desired, - firstAmountMin, - secondAmountMin, - networkFee + chainId = chainId, + tokenBase = tokenBase, + tokenTarget = tokenTarget, + markerAssetDesired = desired, + firstAmountMin = firstAmountMin, + secondAmountMin = secondAmountMin, + networkFee = networkFee ) } catch (t: Throwable) { coroutinesStore.uiScope.launch { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt index 497ff59a1c..c8a9e49c9e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -38,12 +38,11 @@ import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.customColors import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID data class LiquidityRemoveConfirmState( - val assetFromIconUrl: String? = null, - val assetToIconUrl: String? = null, + val assetBaseIconUrl: String? = null, + val assetTargetIconUrl: String? = null, val baseAmount: String = "", val baseFiat: String = "", val targetAmount: String = "", @@ -87,7 +86,7 @@ fun LiquidityRemoveConfirmScreen( MarginVertical(margin = 16.dp) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { AsyncImage( - model = getImageRequest(LocalContext.current, state.assetFromIconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetBaseIconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) @@ -95,7 +94,7 @@ fun LiquidityRemoveConfirmScreen( .zIndex(1f) ) AsyncImage( - model = getImageRequest(LocalContext.current, state.assetToIconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetTargetIconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index b56ec0d2b1..af0b3c648d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -10,7 +10,6 @@ import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.formatPercent import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData @@ -107,8 +106,8 @@ class PoolDetailsPresenter @Inject constructor( val result = poolsInteractor.getUserPoolData(chainId, address, baseTokenId, targetTokenId.fromHex())?.let { PoolDetailsState( - assetFrom = baseAsset, - assetTo = targetAsset, + assetBase = baseAsset, + assetTarget = targetAsset, tvl = null, apy = null ) @@ -119,8 +118,8 @@ class PoolDetailsPresenter @Inject constructor( private fun CommonPoolData.mapToState(): PoolDetailsState { return PoolDetailsState( - assetFrom = basic.baseToken.token.configuration, - assetTo = basic.targetToken?.token?.configuration, + assetBase = basic.baseToken.token.configuration, + assetTarget = basic.targetToken?.token?.configuration, pooledBaseAmount = user?.basePooled?.formatCrypto(basic.baseToken.token.configuration.symbol).orEmpty(), pooledBaseFiat = user?.basePooled?.applyFiatRate(basic.baseToken.token.fiatRate)?.formatFiat(basic.baseToken.token.fiatSymbol).orEmpty(), pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.token?.configuration?.symbol).orEmpty(), diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 1df957a9fe..1b3cd19939 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -40,12 +40,11 @@ import jp.co.soramitsu.common.compose.theme.customColors import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.feature_wallet_impl.R -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID data class PoolDetailsState( - val assetFrom: Asset? = null, - val assetTo: Asset? = null, + val assetBase: Asset? = null, + val assetTarget: Asset? = null, val pooledBaseAmount: String = "", val pooledBaseFiat: String = "", val pooledTargetAmount: String = "", @@ -81,7 +80,7 @@ fun PoolDetailsScreen( Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { AsyncImage( - model = getImageRequest(LocalContext.current, state.assetFrom?.iconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetBase?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) @@ -89,7 +88,7 @@ fun PoolDetailsScreen( .zIndex(1f) ) AsyncImage( - model = getImageRequest(LocalContext.current, state.assetTo?.iconUrl.orEmpty()), + model = getImageRequest(LocalContext.current, state.assetTarget?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) @@ -106,7 +105,7 @@ fun PoolDetailsScreen( horizontalArrangement = Arrangement.Center ) { Text( - text = state.assetFrom?.symbol?.uppercase().orEmpty(), + text = state.assetBase?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -124,7 +123,7 @@ fun PoolDetailsScreen( MarginHorizontal(margin = 8.dp) Text( - text = state.assetTo?.symbol?.uppercase().orEmpty(), + text = state.assetTarget?.symbol?.uppercase().orEmpty(), style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -157,7 +156,7 @@ fun PoolDetailsScreen( if (state.pooledBaseAmount.isNotEmpty()) { InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetFrom?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.pl_your_pooled_format, state.assetBase?.symbol?.uppercase().orEmpty()), value = state.pooledBaseAmount, additionalValue = state.pooledBaseFiat ) @@ -166,7 +165,7 @@ fun PoolDetailsScreen( if (state.pooledTargetAmount.isNotEmpty()) { InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetTo?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.pl_your_pooled_format, state.assetTarget?.symbol?.uppercase().orEmpty()), value = state.pooledTargetAmount, additionalValue = state.pooledTargetFiat ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 5c8d20ac4b..17104cc434 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -38,6 +38,9 @@ class PoolListPresenter @Inject constructor( private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() + .onEach { + stateFlow.value = stateFlow.value.copy(isLoading = true) + } .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) val pools = screenArgsFlow.flatMapLatest { screenArgs -> @@ -61,11 +64,6 @@ class PoolListPresenter @Inject constructor( } } - - init { - - } - private val stateFlow = MutableStateFlow(PoolListState()) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { @@ -75,7 +73,7 @@ class PoolListPresenter @Inject constructor( private fun subscribeState(coroutineScope: CoroutineScope) { pools.onEach { - stateFlow.value = stateFlow.value.copy(pools = it) + stateFlow.value = stateFlow.value.copy(pools = it, isLoading = false) }.launchIn(coroutineScope) enteredAssetQueryFlow.onEach { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 007d3f6fc6..79068924c1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -21,17 +21,18 @@ import jp.co.soramitsu.common.compose.theme.white04 import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItem import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItemState +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.ShimmerPoolList import jp.co.soramitsu.ui_core.resources.Dimens data class PoolListState( val pools: List = listOf(), - val searchQuery: String? = null + val searchQuery: String? = null, + val isLoading: Boolean = true ) interface PoolListScreenInterface { fun onPoolClicked(pair: StringPair) fun onAssetSearchEntered(value: String) - } @Composable @@ -54,20 +55,23 @@ fun PoolListScreen( onInput = callback::onAssetSearchEntered ) } + if (state.isLoading) { + ShimmerPoolList(20) + } else { + val listState = rememberLazyListState() - val listState = rememberLazyListState() - - LazyColumn( - state = listState, - modifier = Modifier - .wrapContentHeight() - ) { - items(state.pools) { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, - ) + LazyColumn( + state = listState, + modifier = Modifier + .wrapContentHeight() + ) { + items(state.pools) { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index 570c8fef94..1c28037502 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -9,31 +9,31 @@ import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus class ValidateAddLiquidityUseCase @Inject constructor() { operator fun invoke( - assetFrom: AssetWithStatus, - assetTo: AssetWithStatus, + assetBase: AssetWithStatus, + assetTarget: AssetWithStatus, utilityAssetId: String, utilityAmount: BigDecimal, - amountFrom: BigDecimal, - amountTo: BigDecimal, + amountBase: BigDecimal, + amountTarget: BigDecimal, feeAmount: BigDecimal ): Result { return runCatching { - val isEnoughAmountFrom = amountFrom + feeAmount.takeIf { - assetFrom.asset.token.configuration.id == utilityAssetId - }.orZero() < assetFrom.asset.total.orZero() + val isEnoughAmountBase = amountBase + feeAmount.takeIf { + assetBase.asset.token.configuration.id == utilityAssetId + }.orZero() < assetBase.asset.total.orZero() - val isEnoughAmountTo = amountTo + feeAmount.takeIf { - assetTo.asset.token.configuration.id == utilityAssetId - }.orZero() < assetTo.asset.total.orZero() + val isEnoughAmountTarget = amountTarget + feeAmount.takeIf { + assetTarget.asset.token.configuration.id == utilityAssetId + }.orZero() < assetTarget.asset.total.orZero() - val isEnoughAmountFee = if (utilityAssetId in listOf(assetFrom.asset.token.configuration.id, assetTo.asset.token.configuration.id)) { + val isEnoughAmountFee = if (utilityAssetId in listOf(assetBase.asset.token.configuration.id, assetTarget.asset.token.configuration.id)) { true } else { feeAmount < utilityAmount } val validationChecks = mapOf ( - TransferValidationResult.InsufficientBalance to (!isEnoughAmountFrom || !isEnoughAmountTo), + TransferValidationResult.InsufficientBalance to (!isEnoughAmountBase || !isEnoughAmountTarget), TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt index 2f342f54e8..344353145a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt @@ -2,9 +2,7 @@ package jp.co.soramitsu.liquiditypools.impl.usecase import java.math.BigDecimal import javax.inject.Inject -import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.wallet.api.domain.TransferValidationResult -import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus class ValidateRemoveLiquidityUseCase @Inject constructor() { @@ -12,19 +10,19 @@ class ValidateRemoveLiquidityUseCase @Inject constructor() { utilityAmount: BigDecimal, userBasePooled: BigDecimal, userTargetPooled: BigDecimal, - amountFrom: BigDecimal, - amountTo: BigDecimal, + amountBase: BigDecimal, + amountTarget: BigDecimal, feeAmount: BigDecimal ): Result { return runCatching { - val isEnoughAmountFrom = amountFrom <= userBasePooled + val isEnoughAmountBase = amountBase <= userBasePooled - val isEnoughAmountTo = amountTo <= userTargetPooled + val isEnoughAmountTarget = amountTarget <= userTargetPooled val isEnoughAmountFee = feeAmount < utilityAmount val validationChecks = mapOf ( - TransferValidationResult.InsufficientBalance to (!isEnoughAmountFrom || !isEnoughAmountTo), + TransferValidationResult.InsufficientBalance to (!isEnoughAmountBase || !isEnoughAmountTarget), TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee ) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index a0b5a62939..48a1f379d1 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -1024,7 +1024,7 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT } override suspend fun updateAccountPools(chainId: ChainId, address: String) { - println("!!! call blockExplorerManager.updatePoolsSbApy()") + println("!!! call blockExplorerManager.updateAccountPools()") blockExplorerManager.updatePoolsSbApy() val pools = mutableListOf() @@ -1032,6 +1032,7 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT val assets = chainRegistry.getChain(chainId).assets val tokenIds = getUserPoolsTokenIds(chainId, address) + println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") tokenIds.forEach { (baseTokenId, tokensId) -> val baseToken = assets.firstOrNull { @@ -1108,6 +1109,7 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT } override suspend fun updateBasicPools(chainId: ChainId) { + println("!!! pswapRepo updateBasicPools") val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storage = runtimeOrNull?.metadata ?.module(Modules.POOL_XYK) @@ -1162,6 +1164,7 @@ override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetT list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null } poolDao.deleteBasicPools(minus) + println("!!! pswapRepo insertBasicPools(list) size = ${list.size}") poolDao.insertBasicPools(list) } val assetsFlow = accountRepository.selectedMetaAccountFlow() From 1349bd60a44e96c44f3d603bd1a986aa537f5907 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 31 Jul 2024 22:54:35 +0500 Subject: [PATCH 38/84] refactor: pools chainId location; pools repository --- .../liquiditypools/data/PoolsRepository.kt | 90 ++ .../domain/interfaces/PoolsInteractor.kt | 5 +- .../navigation/InternalPoolsRouter.kt | 13 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 10 +- feature-liquiditypools-impl/build.gradle.kts | 1 + .../impl/data/DemeterFarmingRepositoryImpl.kt | 6 +- .../impl/data/PoolsRepositoryImpl.kt | 1131 +++++++++++++++++ .../liquiditypools/impl/di/PoolsModule.kt | 40 +- .../impl/domain/PoolsInteractorImpl.kt | 57 +- .../navigation/InternalPoolsRouterImpl.kt | 27 +- .../impl/presentation/PoolsFlowViewModel.kt | 4 +- .../allpools/AllPoolsPresenter.kt | 14 +- .../liquidityadd/LiquidityAddPresenter.kt | 48 +- .../LiquidityAddConfirmPresenter.kt | 17 +- .../LiquidityRemovePresenter.kt | 33 +- .../LiquidityRemoveConfirmPresenter.kt | 17 +- .../pooldetails/PoolDetailsPresenter.kt | 15 +- .../poollist/PoolListPresenter.kt | 4 +- .../polkaswap/api/data/PolkaswapRepository.kt | 67 - .../api/domain/PolkaswapInteractor.kt | 2 - .../impl/data/PolkaswapRepositoryImpl.kt | 1072 ---------------- .../impl/di/PolkaswapFeatureBindModule.kt | 10 - .../impl/domain/PolkaswapInteractorImpl.kt | 7 - 23 files changed, 1374 insertions(+), 1316 deletions(-) create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt new file mode 100644 index 0000000000..38b8c81c4f --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -0,0 +1,90 @@ +package jp.co.soramitsu.liquiditypools.data + +import java.math.BigDecimal +import java.math.BigInteger +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.polkaswap.api.models.Market +import jp.co.soramitsu.polkaswap.api.models.WithDesired +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId +import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair +import kotlinx.coroutines.flow.Flow + +interface PoolsRepository { + + val poolsChainId: String + + suspend fun isPairAvailable( + chainId: ChainId, + tokenFromId: String, + tokenToId: String, + dexId: Int + ): Boolean + + suspend fun getBasicPools(chainId: ChainId): List + + suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? + + suspend fun getUserPoolData( + chainId: ChainId, + address: String, + baseTokenId: String, + targetTokenId: ByteArray + ): PoolDataDto? + + suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, + address: String, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? + + suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + ): BigDecimal? + + suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int + + fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? + + suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal + ): Result? + + suspend fun observeAddLiquidity( + chainId: ChainId, + address: String, + keypair: Keypair, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): Result? + + suspend fun updateAccountPools(chainId: ChainId, address: String) + suspend fun updateBasicPools(chainId: ChainId) + +// fun subscribePools(): Flow> + fun subscribePools(address: String): Flow> + fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow + +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 073bfadee4..6da6852716 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -11,14 +11,13 @@ import kotlinx.coroutines.flow.Flow interface PoolsInteractor { val poolsChainId: String - suspend fun getBasicPools(chainId: ChainId): List + suspend fun getBasicPools(): List // fun subscribePoolsCache(): Flow> // suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? fun subscribePoolsCacheOfAccount(address: String): Flow> fun subscribePoolsCacheCurrentAccount(): Flow> suspend fun getPoolData( - chainId: ChainId, baseTokenId: String, targetTokenId: String, ): Flow @@ -43,13 +42,11 @@ interface PoolsInteractor { ): BigDecimal? suspend fun calcRemoveLiquidityNetworkFee( - chainId: ChainId, tokenBase: Asset, tokenTarget: Asset, ): BigDecimal? suspend fun isPairEnabled( - chainId: ChainId, baseTokenId: String, targetTokenId: String ): Boolean diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 2defd9c7db..781bc65118 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -11,15 +11,14 @@ interface InternalPoolsRouter { fun back() fun popupToScreen(route: LiquidityPoolsNavGraphRoute) - fun openAllPoolsScreen(chainId: ChainId) - fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) + fun openAllPoolsScreen() + fun openDetailsPoolScreen(ids: StringPair) - fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) - fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) + fun openAddLiquidityScreen(ids: StringPair) + fun openAddLiquidityConfirmScreen(ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) - fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) + fun openRemoveLiquidityScreen(ids: StringPair) fun openRemoveLiquidityConfirmScreen( - chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, @@ -28,7 +27,7 @@ interface InternalPoolsRouter { desired: BigDecimal ) - fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) + fun openPoolListScreen(isUserPools: Boolean) fun openErrorsScreen(title: String? = null, message: String) fun openInfoScreen(title: String, message: String) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 1b40588c75..58994f5b96 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -12,16 +12,13 @@ sealed interface LiquidityPoolsNavGraphRoute { override val routeName: String = "Loading" } - class AllPoolsScreen( - val chainId: ChainId - ): LiquidityPoolsNavGraphRoute by Companion { + class AllPoolsScreen: LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "AllPoolsScreen" } } class ListPoolsScreen( - val chainId: ChainId, val isUserPools: Boolean ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -30,7 +27,6 @@ sealed interface LiquidityPoolsNavGraphRoute { } class PoolDetailsScreen( - val chainId: ChainId, val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -39,7 +35,6 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityAddScreen( - val chainId: ChainId, val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -48,7 +43,6 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityAddConfirmScreen( - val chainId: ChainId, val ids: StringPair, val amountBase: BigDecimal, val amountTarget: BigDecimal, @@ -60,7 +54,6 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityRemoveScreen( - val chainId: ChainId, val ids: StringPair ) : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { @@ -69,7 +62,6 @@ sealed interface LiquidityPoolsNavGraphRoute { } class LiquidityRemoveConfirmScreen( - val chainId: ChainId, val ids: StringPair, val amountBase: BigDecimal, val amountTarget: BigDecimal, diff --git a/feature-liquiditypools-impl/build.gradle.kts b/feature-liquiditypools-impl/build.gradle.kts index 2a04356b9a..7f88ab50c2 100644 --- a/feature-liquiditypools-impl/build.gradle.kts +++ b/feature-liquiditypools-impl/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { // implementation(libs.navigation.compose) implementation(libs.sora.ui) + implementation(libs.room.ktx) // // implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.7.3") // diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt index 1680429121..86bdae8927 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt @@ -13,9 +13,9 @@ import jp.co.soramitsu.androidfoundation.format.safeCast import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.common.data.network.rpc.retrieveAllValues import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository +import jp.co.soramitsu.liquiditypools.data.PoolsRepository import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingBasicPool import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool -import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId @@ -37,7 +37,7 @@ class DemeterFarmingRepositoryImpl( private val bulkRetriever: BulkRetriever, private val accountRepository: AccountRepository, private val walletRepository: WalletRepository, - private val polkaswapRepository: PolkaswapRepository, + private val poolsRepository: PoolsRepository, ) : DemeterFarmingRepository { companion object { @@ -113,7 +113,7 @@ class DemeterFarmingRepositoryImpl( val poolTokenPrice = poolTokenMapped.token.fiatRate.orZero() val rewardTokenPrice = rewardTokenMapped.token.fiatRate.orZero() val tvl = if (basic.isFarm) { - polkaswapRepository.getBasicPool(chainId, basic.base, basic.pool)?.let { pool -> + poolsRepository.getBasicPool(chainId, basic.base, basic.pool)?.let { pool -> val kf = pool.targetReserves.div(pool.totalIssuance) kf.times(total).times(2.toBigDecimal()).times(poolTokenPrice) } ?: BigDecimal.ZERO diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt new file mode 100644 index 0000000000..f34cccde22 --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -0,0 +1,1131 @@ +package jp.co.soramitsu.liquiditypools.impl.data + +import androidx.room.withTransaction +import java.math.BigDecimal +import java.math.BigInteger +import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.accountId +import jp.co.soramitsu.androidfoundation.format.addHexPrefix +import jp.co.soramitsu.androidfoundation.format.mapBalance +import jp.co.soramitsu.androidfoundation.format.safeCast +import jp.co.soramitsu.common.utils.Modules +import jp.co.soramitsu.common.utils.fromHex +import jp.co.soramitsu.common.utils.mapList +import jp.co.soramitsu.common.utils.poolTBC +import jp.co.soramitsu.common.utils.poolXYK +import jp.co.soramitsu.core.extrinsic.ExtrinsicService +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse +import jp.co.soramitsu.core.utils.utilityAsset +import jp.co.soramitsu.coredb.AppDatabase +import jp.co.soramitsu.coredb.dao.PoolDao +import jp.co.soramitsu.coredb.model.BasicPoolLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal +import jp.co.soramitsu.coredb.model.UserPoolJoinedLocalNullable +import jp.co.soramitsu.coredb.model.UserPoolLocal +import jp.co.soramitsu.liquiditypools.data.PoolsRepository +import jp.co.soramitsu.polkaswap.api.data.PoolDataDto +import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData +import jp.co.soramitsu.polkaswap.api.models.Market +import jp.co.soramitsu.polkaswap.api.models.WithDesired +import jp.co.soramitsu.polkaswap.api.models.backStrings +import jp.co.soramitsu.polkaswap.api.models.toFilters +import jp.co.soramitsu.polkaswap.api.models.toMarkets +import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.liquidityAdd +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.register +import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.removeLiquidity +import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas +import jp.co.soramitsu.runtime.ext.addressOf +import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId +import jp.co.soramitsu.runtime.network.subscriptionFlowCatching +import jp.co.soramitsu.runtime.storage.source.StorageDataSource +import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair +import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.shared_utils.extensions.toHexString +import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct +import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex +import jp.co.soramitsu.shared_utils.runtime.metadata.module +import jp.co.soramitsu.shared_utils.runtime.metadata.storage +import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey +import jp.co.soramitsu.shared_utils.scale.Schema +import jp.co.soramitsu.shared_utils.scale.dataType.uint32 +import jp.co.soramitsu.shared_utils.scale.sizedByteArray +import jp.co.soramitsu.shared_utils.scale.uint128 +import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId +import jp.co.soramitsu.shared_utils.wsrpc.exception.RpcException +import jp.co.soramitsu.shared_utils.wsrpc.executeAsync +import jp.co.soramitsu.shared_utils.wsrpc.mappers.nonNull +import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo +import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojoList +import jp.co.soramitsu.shared_utils.wsrpc.mappers.scale +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest +import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest +import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange +import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository +import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks +import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach + +class PoolsRepositoryImpl @Inject constructor( + private val extrinsicService: ExtrinsicService, + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository, + private val blockExplorerManager: BlockExplorerManager, + private val poolDao: PoolDao, + private val db: AppDatabase, +) : PoolsRepository { + override val poolsChainId = soraTestChainId + + override suspend fun isPairAvailable( + chainId: ChainId, + tokenFromId: String, + tokenToId: String, + dexId: Int + ): Boolean { + val request = RuntimeRequest( + method = "liquidityProxy_isPathAvailable", + params = listOf( + dexId, + tokenFromId, + tokenToId + ) + ) + + return chainRegistry.awaitConnection(chainId).socketService.executeAsync(request, mapper = pojo().nonNull()) + } + + fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } + fun String.mapAssetId() = this.fromHex().mapAssetId() + fun String.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) + ) + + fun RuntimeSnapshot.reservesKeyToken(baseTokenId: String): String = + this.metadata.module(Modules.POOL_XYK) + .storage("Reserves") + .storageKey( + this, + baseTokenId.mapCodeToken(), + ) + + suspend fun getStorageHex(chainId: ChainId, storageKey: String): String? = + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo(), + ).result + + suspend fun getStateKeys(chainId: ChainId, partialKey: String): List = + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = StateKeys(listOf(partialKey)), + mapper = pojoList(), + ).result ?: emptyList() + + class StateKeys(params: List) : RuntimeRequest("state_getKeys", params) + + fun ByteArray.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) + ) + + object PoolPropertiesResponse : Schema() { + val first by sizedByteArray(32) + val second by sizedByteArray(32) + } + + suspend fun getPoolReserveAccount( + chainId: ChainId, + baseTokenId: String, + tokenId: ByteArray + ): ByteArray? { + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) + val storageKey = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Properties")?.storageKey( + runtimeOrNull, + baseTokenId.mapCodeToken(), + tokenId.mapCodeToken(), + ) + ?: return null + + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(PoolPropertiesResponse), + ) + .result + ?.let { storage -> + storage[storage.schema.first] + } + } + + fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() + + object TotalIssuance : Schema() { + val totalIssuance by uint128() + } + + suspend fun getPoolTotalIssuances( + chainId: ChainId, + reservesAccountId: ByteArray, + ): BigInteger? { + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) + val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) + ?.storage("TotalIssuances") + ?.storageKey(runtimeOrNull, reservesAccountId) + ?: return null + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(TotalIssuance), + ) + .result + ?.let { storage -> + storage[storage.schema.totalIssuance] + } + } + + override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { + val tempApy = blockExplorerManager.getTempApy(reserveAccountOfPool) + return tempApy + } + + override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? { + val poolLocal = poolDao.getBasicPool(baseTokenId, targetTokenId) ?: return null + + val soraChain = chainRegistry.getChain(chainId) + val wallet = accountRepository.getSelectedMetaAccount() + val accountId = wallet.accountId(soraChain) + val soraAssets = soraChain.assets.mapNotNull { chainAsset -> + accountId?.let { + walletRepository.getAsset( + metaId = wallet.id, + accountId = accountId, + chainAsset = chainAsset, + minSupportedVersion = null + ) + } + } + + val baseAsset = soraAssets.firstOrNull { + it.token.configuration.currencyId == baseTokenId + } ?: return null + val targetAsset = soraAssets.firstOrNull { + it.token.configuration.currencyId == targetTokenId + } + + return BasicPoolData( + baseToken = baseAsset, + targetToken = targetAsset, + baseReserves = poolLocal.reserveBase, + targetReserves = poolLocal.reserveTarget, + totalIssuance = poolLocal.totalIssuance, + reserveAccount = poolLocal.reservesAccount, + sbapy = getPoolStrategicBonusAPY(poolLocal.reservesAccount) + ) + } + + override suspend fun getBasicPools(chainId: ChainId): List { + println("!!! getBasicPools() start") + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) + val storage = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Reserves") + + val list = mutableListOf() + + val soraChain = chainRegistry.getChain(chainId) + + val wallet = accountRepository.getSelectedMetaAccount() + + val accountId = wallet.accountId(soraChain) + val soraAssets = soraChain.assets.mapNotNull { chainAsset -> + accountId?.let { + walletRepository.getAsset( + metaId = wallet.id, + accountId = accountId, + chainAsset = chainAsset, + minSupportedVersion = null + ) + } + } + + println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") + + soraAssets.forEach { asset -> + val currencyId = asset.token.configuration.currencyId + val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } + key?.let { + getStateKeys(chainId, it).forEach { storageKey -> + val targetToken = storageKey.assetIdFromKey() + getStorageHex(chainId, storageKey)?.let { storageHex -> + storage?.type?.value + ?.fromHex(runtimeOrNull, storageHex) + ?.safeCast>()?.let { reserves -> + + val reserveAccount = getPoolReserveAccount( + chainId, + currencyId, + targetToken.fromHex() + ) + val total = reserveAccount?.let { + getPoolTotalIssuances(chainId, it) + }?.let { + mapBalance(it, asset.token.configuration.precision) + } + val targetAsset = soraAssets.firstOrNull { it.token.configuration.currencyId == targetToken } + val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" + + val element = BasicPoolData( + baseToken = asset, + targetToken = targetAsset, + baseReserves = mapBalance(reserves[0], asset.token.configuration.precision), + targetReserves = mapBalance(reserves[1], asset.token.configuration.precision), + totalIssuance = total ?: BigDecimal.ZERO, + reserveAccount = reserveAccountAddress, + sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) + ) + + println("!!! getBasicPools() list.add(BasicPoolData: $element") + list.add( + element + ) + } + } + } + } + } + + println("!!! getBasicPools() return list.size = ${list.size}") + + return list + } + + private fun subscribeAccountPoolProviders( + chainId: ChainId, + address: String, + reservesAccount: ByteArray, + ): Flow = flow { + val poolProvidersKey = + chainRegistry.getRuntimeOrNull(chainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("TotalIssuances") + .storageKey( + it, + reservesAccount, + address.toAccountId() + ) + } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") + val poolProvidersFlow = chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(poolProvidersKey), + "state_unsubscribeStorage", + ).map { + it.map { it.storageChange().getSingleChange().orEmpty() } + }.map { + it.getOrNull().orEmpty() + } + emitAll(poolProvidersFlow) + } + + override suspend fun getUserPoolData( + chainId: ChainId, + address: String, + baseTokenId: String, + targetTokenId: ByteArray + ): PoolDataDto? { + val reserves = getPairWithXorReserves(chainId, baseTokenId, targetTokenId) + val totalIssuanceAndProperties = + getPoolTotalIssuanceAndProperties(chainId, baseTokenId, targetTokenId, address) + + if (reserves == null || totalIssuanceAndProperties == null) { + return null + } + val reservesAccount = chainRegistry.getChain(chainId).addressOf(totalIssuanceAndProperties.third) + + return PoolDataDto( + baseTokenId, + targetTokenId.toHexString(true), + reserves.first, + reserves.second, + totalIssuanceAndProperties.first, + totalIssuanceAndProperties.second, + reservesAccount, + ) + } + + override suspend fun calcAddLiquidityNetworkFee( + chainId: ChainId, + address: String, + tokenBase: Asset, + tokenTarget: Asset, + tokenBaseAmount: BigDecimal, + tokenTargetAmount: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): BigDecimal? { + val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenBaseAmount, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenTargetAmount, slippageTolerance) + val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) + val chain = chainRegistry.getChain(chainId) + + val fee = extrinsicService.estimateFee(chain) { + liquidityAdd( + dexId = dexId, + baseTokenId = tokenBase.currencyId, + targetTokenId = tokenTarget.currencyId, + pairPresented = pairPresented, + pairEnabled = pairEnabled, + tokenBaseAmount = tokenBase.planksFromAmount(tokenBaseAmount), + tokenTargetAmount = tokenTarget.planksFromAmount(tokenTargetAmount), + amountBaseMin = tokenBase.planksFromAmount(amountFromMin), + amountTargetMin = tokenTarget.planksFromAmount(amountToMin), + ) + } + + val feeToken = chain.utilityAsset + return feeToken?.amountFromPlanks(fee) + } + + override suspend fun calcRemoveLiquidityNetworkFee( + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + ): BigDecimal? { + val chain = chainRegistry.getChain(chainId) + val baseTokenId = tokenBase.currencyId ?: return null + val targetTokenId = tokenTarget.currencyId ?: return null + + val fee = extrinsicService.estimateFee(chain) { + removeLiquidity( + dexId = getPoolBaseTokenDexId(chainId, baseTokenId), + outputAssetIdA = baseTokenId, + outputAssetIdB = targetTokenId, + markerAssetDesired = tokenBase.planksFromAmount(BigDecimal.ONE), + outputAMin = tokenBase.planksFromAmount(BigDecimal.ONE), + outputBMin = tokenTarget.planksFromAmount(BigDecimal.ONE), + ) + } + val feeToken = chain.utilityAsset + return feeToken?.amountFromPlanks(fee) + } + + override suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int { + return getPoolBaseTokens(chainId).first { + it.second == tokenId + }.first + } + + private suspend fun getPoolBaseTokens(chainId: ChainId): List> { + val runtimeSnapshot = chainRegistry.getRuntimeOrNull(chainId) + val metadataStorage = runtimeSnapshot?.metadata + ?.module("DEXManager") + ?.storage("DEXInfos") + val partialKey = metadataStorage + ?.storageKey() ?: error("getPoolBaseTokenDexId storageKey not supported") + + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = StateKeys(listOf(partialKey)), + mapper = pojoList().nonNull() + ).let { storageKeys -> + storageKeys.mapNotNull { storageKey -> + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull() + ).let { storage -> + val storageType = metadataStorage.type.value!! + val storageRawData = + storageType.fromHex(runtimeSnapshot, storage) + (storageRawData as? Struct.Instance)?.let { instance -> + instance.mapToToken("baseAssetId")?.let { token -> + storageKey.takeInt32() to token + } + } + } + } + } + } + + fun Struct.Instance.mapToToken(field: String) = + this.get(field)?.getTokenId()?.toHexString(true) + + fun String.takeInt32() = uint32.fromHex(this.takeLast(8)).toInt() + + private suspend fun getPoolTotalIssuanceAndProperties( + chainId: ChainId, + baseTokenId: String, + tokenId: ByteArray, + address: String + ): Triple? { + return getPoolReserveAccount(chainId, baseTokenId, tokenId)?.let { account -> + getPoolTotalIssuances( + chainId, + account + )?.let { + val provider = getPoolProviders( + chainId, + account, + address + ) + Triple(it, provider, account) + } + } + } + + object PoolProviders : Schema() { + val poolProviders by uint128() + } + + private suspend fun getPoolProviders( + chainId: ChainId, + reservesAccountId: ByteArray, + currentAddress: String + ): BigInteger { + val storageKey = + chainRegistry.getRuntimeOrNull(chainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("PoolProviders").storageKey( + it, + reservesAccountId, + currentAddress.toAccountId() + ) + } ?: return BigInteger.ZERO + return runCatching { + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(PoolProviders), + ) + .let { storage -> + storage.result?.let { + it[it.schema.poolProviders] + } ?: BigInteger.ZERO + } + }.getOrElse { + it.printStackTrace() + throw it + } + } + + object ReservesResponse : Schema() { + val first by uint128() + val second by uint128() + } + + private suspend fun getPairWithXorReserves( + chainId: ChainId, + baseTokenId: String, + tokenId: ByteArray + ): Pair? { + val storageKey = + chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) + ?: return null + return try { + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(ReservesResponse), + ) + .result + ?.let { storage -> + storage[storage.schema.first] to storage[storage.schema.second] + } + } catch (e: Exception) { + println("!!! getPairWithXorReserves error = ${e.message}") + + e.printStackTrace() + throw e + } + } + +// override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { +// return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { +// it.user.address == address +// } +// } + +// suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { +//// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) +//// val socketService = chainRegistry.getConnection(chainId).socketService +//// socketService.executeAsyncCatching() +// val tokensPair: List>>? = address?.let { +// getUserPoolsTokenIds(it) +// } +// +// val pools = mutableListOf() +// +// tokensPair?.forEach { (baseTokenId, tokensId) -> +// tokensId.mapNotNull { tokenId -> +// getUserPoolData(address, baseTokenId, tokenId) +// }.forEach pool@{ poolDataDto -> +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// val token = assets.firstOrNull { +// it.token.configuration.currencyId == poolDataDto.assetId +// } ?: return@pool +// val baseToken = assets.firstOrNull { +// it.token.configuration.currencyId == baseTokenId +// } ?: return@pool +// val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 +// val tokenPrecision = token?.token?.configuration?.precision ?: 0 +// +// val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) +// +// val basePooled = PolkaswapFormulas.calculatePooledValue( +// mapBalance( +// poolDataDto.reservesFirst, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// baseToken?.token?.configuration?.precision, +// ) +// val secondPooled = PolkaswapFormulas.calculatePooledValue( +// mapBalance( +// poolDataDto.reservesSecond, +// tokenPrecision +// ), +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// token?.token?.configuration?.precision, +// ) +// val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// ) +// val userPoolData = CommonUserPoolData( +// basic = BasicPoolData( +// baseToken = baseToken, +// targetToken = token, +// baseReserves = mapBalance( +// poolDataDto.reservesFirst, +// xorPrecision +// ), +// targetReserves = mapBalance( +// poolDataDto.reservesSecond, +// tokenPrecision +// ), +// totalIssuance = mapBalance( +// poolDataDto.totalIssuance, +// xorPrecision +// ), +// reserveAccount = poolDataDto.reservesAccount, +// sbapy = apy, +// ), +// user = UserPoolData( +//// address = address, +// basePooled = basePooled, +// targetPooled = secondPooled, +// share, +// mapBalance( +// poolDataDto.poolProvidersBalance, +// xorPrecision +// ), +// ), +// ) +// pools.add(userPoolData) +// } +// } +// return pools +// } + + fun RuntimeSnapshot.reservesKey(baseTokenId: String, tokenId: ByteArray): String = + this.metadata.module(Modules.POOL_XYK) + .storage("Reserves") + .storageKey( + this, + baseTokenId.mapCodeToken(), + tokenId.mapCodeToken(), + ) + + @Suppress("UNCHECKED_CAST") + fun SubscriptionChange.storageChange(): SubscribeStorageResult { + val result = params.result as? Map<*, *> ?: throw IllegalArgumentException("${params.result} is not a valid storage result") + + val block = result["block"] as? String ?: throw IllegalArgumentException("$result is not a valid storage result") + val changes = result["changes"] as? List> ?: throw IllegalArgumentException("$result is not a valid storage result") + + return SubscribeStorageResult(block, changes) + } + + private fun subscribeToPoolData( + chainId: ChainId, + baseTokenId: String, + tokenId: ByteArray, + reservesAccount: ByteArray, + ): Flow = flow { + val reservesKey = + chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) + + val reservesFlow = reservesKey?.let { + chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(reservesKey), + "state_unsubscribeStorage", + ).map { + it.map { it.storageChange().getSingleChange().orEmpty() } + }.map { + it.getOrNull().orEmpty() + } + } ?: emptyFlow() + + val totalIssuanceKey = + chainRegistry.getRuntimeOrNull(chainId)?.let { + it.metadata.module(Modules.POOL_XYK) + .storage("TotalIssuances") + .storageKey(it, reservesAccount) + } + val totalIssuanceFlow = totalIssuanceKey?.let { + chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(totalIssuanceKey), + "state_unsubscribeStorage", + ).map { + it.getOrNull()?.storageChange()?.getSingleChange().orEmpty() + } + } ?: emptyFlow() + + val resultFlow = reservesFlow + .combine(totalIssuanceFlow) { reservesString, totalIssuanceString -> + (reservesString + totalIssuanceString).take(5) + } + + emitAll(resultFlow) + } + + // changes are in format [[storage key, value], [..], ..] + class SubscribeStorageResult(val block: String, val changes: List>) { + fun getSingleChange() = changes.first()[1] + } + + fun Struct.Instance.getTokenId() = get>("code") + ?.map { (it as BigInteger).toByte() } + ?.toByteArray() + + + suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { + val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) + return runCatching { + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = StateKeys(listOfNotNull(accountPoolsKey)), + mapper = pojoList().nonNull() + ) + }.onFailure { + println("!!! getUserPoolsTokenIdsKeys error: ${it.message}") + it.printStackTrace() + } + .getOrThrow() + } + + suspend fun getUserPoolsTokenIds( + chainId: ChainId, + address: String + ): List>> { + return runCatching { + val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) + storageKeys.map { storageKey -> + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull(), + ) + .let { storage -> + val storageType = + chainRegistry.getRuntimeOrNull(chainId)?.metadata?.module(Modules.POOL_XYK) + ?.storage("AccountPools")?.type?.value!! + val storageRawData = + storageType.fromHex(chainRegistry.getRuntimeOrNull(chainId)!!, storage) + val tokens: List = if (storageRawData is List<*>) { + storageRawData.filterIsInstance() + .mapNotNull { struct -> + struct.getTokenId() + } + } else { + emptyList() + } + storageKey.assetIdFromKey() to tokens + } + } + }.getOrThrow() + } + + override suspend fun observeRemoveLiquidity( + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal + ): Result? { + val soraChain = accountRepository.getChain(chainId) + val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId + val baseTokenId = tokenBase.currencyId ?: return null + val targetTokenId = tokenTarget.currencyId ?: return null + + return extrinsicService.submitExtrinsic( + chain = soraChain, + accountId = accountId + ) { + removeLiquidity( + dexId = getPoolBaseTokenDexId(chainId, baseTokenId), + outputAssetIdA = baseTokenId, + outputAssetIdB = targetTokenId, + markerAssetDesired = tokenBase.planksFromAmount(markerAssetDesired), + outputAMin = tokenBase.planksFromAmount(firstAmountMin), + outputBMin = tokenTarget.planksFromAmount(secondAmountMin), + ) + } + } + + override suspend fun observeAddLiquidity( + chainId: ChainId, + address: String, + keypair: Keypair, + tokenBase: Asset, + tokenTarget: Asset, + amountBase: BigDecimal, + amountTarget: BigDecimal, + pairEnabled: Boolean, + pairPresented: Boolean, + slippageTolerance: Double + ): Result? { + val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountBase, slippageTolerance) + val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTarget, slippageTolerance) + val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) + val soraChain = accountRepository.getChain(chainId) + val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId + + val baseTokenId = tokenBase.currencyId + val targetTokenId = tokenTarget.currencyId + if (baseTokenId == null || targetTokenId == null) return null + + return extrinsicService.submitExtrinsic( + chain = soraChain, + accountId = accountId, + useBatchAll = !pairPresented + ) { + if (!pairPresented) { + if (!pairEnabled) { + register( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId + ) + } + initializePool( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId + ) + } + + depositLiquidity( + dexId = dexId, + baseAssetId = baseTokenId, + targetAssetId = targetTokenId, + baseAssetAmount = mapBalance(amountBase, tokenBase.precision), + targetAssetAmount = mapBalance(amountTarget, tokenTarget.precision), + amountFromMin = mapBalance(amountFromMin, tokenBase.precision), + amountToMin = mapBalance(amountToMin, tokenTarget.precision) + ) + } + } + + override suspend fun updateAccountPools(chainId: ChainId, address: String) { + println("!!! call blockExplorerManager.updateAccountPools()") + blockExplorerManager.updatePoolsSbApy() + + val pools = mutableListOf() + + val assets = chainRegistry.getChain(chainId).assets + + val tokenIds = getUserPoolsTokenIds(chainId, address) + println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") + tokenIds.forEach { (baseTokenId, tokensId) -> + + val baseToken = assets.firstOrNull { + it.currencyId == baseTokenId + } ?: return@forEach + + val xorPrecision = baseToken.precision + + tokensId.mapNotNull { tokenId -> + getUserPoolData(chainId, address, baseTokenId, tokenId) + }.forEach pool@{ poolDataDto -> + val token = assets.firstOrNull { + it.currencyId == poolDataDto.assetId + } ?: return@pool + val tokenPrecision = token.precision + + val basicPoolLocal = BasicPoolLocal( + tokenIdBase = baseTokenId, + tokenIdTarget = poolDataDto.assetId, + reserveBase = mapBalance( + poolDataDto.reservesFirst, + xorPrecision + ), + reserveTarget = mapBalance( + poolDataDto.reservesSecond, + tokenPrecision + ), + totalIssuance = mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + reservesAccount = poolDataDto.reservesAccount, + ) + + val userPoolLocal = UserPoolLocal( + accountAddress = address, + userTokenIdBase = baseTokenId, + userTokenIdTarget = poolDataDto.assetId, + poolProvidersBalance = mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ) + ) + + pools.add( + UserPoolJoinedLocal( + userPoolLocal = userPoolLocal, + basicPoolLocal = basicPoolLocal, + ) + ) + } + } + + db.withTransaction { + println("!!! updateAccountPools: poolDao.clearTable(address)") + poolDao.clearTable(address) + println( + "!!! updateAccountPools: poolDao.insertBasicPools() size = ${ + pools.map { + it.basicPoolLocal + }.size + }" + ) + poolDao.insertBasicPools( + pools.map { + it.basicPoolLocal + } + ) + println( + "!!! updateAccountPools: poolDao.insertUSERPools() size = ${ + pools.map { + it.userPoolLocal + }.size + }" + ) + poolDao.insertUserPools( + pools.map { + it.userPoolLocal + } + ) + } + } + + override suspend fun updateBasicPools(chainId: ChainId) { + println("!!! pswapRepo updateBasicPools") + val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) + val storage = runtimeOrNull?.metadata + ?.module(Modules.POOL_XYK) + ?.storage("Reserves") + + val list = mutableListOf() + + val soraChain = chainRegistry.getChain(chainId) + val assets = soraChain.assets + + getPoolBaseTokens(chainId).forEach { (dexId, tokenId) -> + val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@forEach + val key = runtimeOrNull?.reservesKeyToken(tokenId) ?: return@forEach + + getStateKeys(chainId, key).forEach { storageKey -> + val targetToken = storageKey.assetIdFromKey() + getStorageHex(chainId, storageKey)?.let { storageHex -> + storage?.type?.value + ?.fromHex(runtimeOrNull, storageHex) + ?.safeCast>()?.let { reserves -> + + val reserveAccount = getPoolReserveAccount( + chainId, + tokenId, + targetToken.fromHex() + ) + + val total = reserveAccount?.let { + getPoolTotalIssuances(chainId, it) + }?.let { + mapBalance(it, asset.precision) + } + + val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" + + list.add( + BasicPoolLocal( + tokenIdBase = tokenId, + tokenIdTarget = targetToken, + reserveBase = mapBalance(reserves[0], asset.precision), + reserveTarget = mapBalance(reserves[1], asset.precision), + totalIssuance = total ?: BigDecimal.ZERO, + reservesAccount = reserveAccountAddress, + ) + ) + } + } + } + } + + val minus = poolDao.getBasicPools().filter { db -> + list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null + } + poolDao.deleteBasicPools(minus) + println("!!! pswapRepo insertBasicPools(list) size = ${list.size}") + poolDao.insertBasicPools(list) + } + + val assetsFlow = accountRepository.selectedMetaAccountFlow() + .distinctUntilChanged { old, new -> old.id != new.id } + .flatMapLatest { meta -> + walletRepository.assetsFlow(meta) + }.mapList { it.asset } + + @OptIn(FlowPreview::class) + override fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow { + return combine( + poolDao.subscribePool(address, baseTokenId, targetTokenId), + assetsFlow + ) { pool, assets -> + mapPoolLocalToData(pool, assets) + } + .mapNotNull { it } + .debounce(500) + +// return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// mapPoolLocalToData(it, assets) +// }.debounce(500) + } + + @OptIn(FlowPreview::class) + override fun subscribePools(address: String): Flow> { + println("!!! repoImpl call subscribePools for address: $address") + + return combine( + poolDao.subscribeAllPools(address), + assetsFlow + ) { pools, assets -> + println("!!! repoImpl subscribePools pools: ${pools.size}") + val mapNotNull = pools.mapNotNull { poolLocal -> + mapPoolLocalToData(poolLocal, assets) + } + println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") + mapNotNull + } + .debounce(500) + +// return poolDao.subscribeAllPools(address).map { pools -> +// println("!!! repoImpl subscribePools pools: ${pools.size}") +// val metaId = accountRepository.getSelectedLightMetaAccount().id +// val assets = walletRepository.getAssets(metaId) +// val mapNotNull = pools.mapNotNull { poolLocal -> +// mapPoolLocalToData(poolLocal, assets) +// } +// println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") +// mapNotNull +// }.debounce(500) + .onEach { + println("!!! repoImpl .debounce(500) pools: ${it.size}") + } + } + + fun RuntimeSnapshot.accountPoolsKey(address: String): String = + this.metadata.module(Modules.POOL_XYK) + .storage("AccountPools") + .storageKey(this, address.toAccountId()) + + private fun mapPoolLocalToData( + poolLocal: UserPoolJoinedLocalNullable, + assets: List + ): CommonPoolData? { + val baseToken = assets.firstOrNull { + it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdBase + } ?: return null + val token = assets.firstOrNull { + it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdTarget + } ?: return null + + val basicPoolData = BasicPoolData( + baseToken = baseToken, + targetToken = token, + baseReserves = poolLocal.basicPoolLocal.reserveBase, + targetReserves = poolLocal.basicPoolLocal.reserveTarget, + totalIssuance = poolLocal.basicPoolLocal.totalIssuance, + reserveAccount = poolLocal.basicPoolLocal.reservesAccount, + sbapy = getPoolStrategicBonusAPY(poolLocal.basicPoolLocal.reservesAccount), + ) + + val userPoolData = poolLocal.userPoolLocal?.let { userPoolLocal -> + val basePooled = PolkaswapFormulas.calculatePooledValue( + poolLocal.basicPoolLocal.reserveBase, + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + baseToken.token.configuration.precision, + ) + val secondPooled = PolkaswapFormulas.calculatePooledValue( + poolLocal.basicPoolLocal.reserveTarget, + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + token.token.configuration.precision, + ) + val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( + userPoolLocal.poolProvidersBalance, + poolLocal.basicPoolLocal.totalIssuance, + ) + UserPoolData( + basePooled = basePooled, + targetPooled = secondPooled, + poolShare = share, + poolProvidersBalance = userPoolLocal.poolProvidersBalance, + ) + } + return CommonPoolData( + basic = basicPoolData, + user = userPoolData, + ) + } + +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index fc45e07a1c..5b05a2bb0c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -4,21 +4,30 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Named import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.rpc.BulkRetriever +import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider +import jp.co.soramitsu.coredb.AppDatabase +import jp.co.soramitsu.coredb.dao.PoolDao import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository +import jp.co.soramitsu.liquiditypools.data.PoolsRepository import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.data.DemeterFarmingRepositoryImpl +import jp.co.soramitsu.liquiditypools.impl.data.PoolsRepositoryImpl import jp.co.soramitsu.liquiditypools.impl.domain.DemeterFarmingInteractorImpl import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor +import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager +import jp.co.soramitsu.runtime.di.REMOTE_STORAGE_SOURCE import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter @@ -29,13 +38,12 @@ class PoolsModule { @Provides @Singleton fun providesPoolInteractor( - polkaswapRepository: PolkaswapRepository, + poolsRepository: PoolsRepository, accountRepository: AccountRepository, - polkaswapInteractor: PolkaswapInteractor, chainRegistry: ChainRegistry, keypairProvider: KeypairProvider ): PoolsInteractor = - PoolsInteractorImpl(polkaswapRepository, accountRepository, polkaswapInteractor, chainRegistry, keypairProvider) + PoolsInteractorImpl(poolsRepository, accountRepository, chainRegistry, keypairProvider) @Provides @Singleton @@ -57,13 +65,35 @@ class PoolsModule { bulkRetriever: BulkRetriever, accountRepository: AccountRepository, walletRepository: WalletRepository, - polkaswapRepository: PolkaswapRepository, + poolsRepository: PoolsRepository, ) : DemeterFarmingRepository = DemeterFarmingRepositoryImpl( chainRegistry, bulkRetriever, accountRepository, walletRepository, - polkaswapRepository + poolsRepository ) + + @Provides + @Singleton + fun providePoolsRepositoryImpl( + extrinsicService: ExtrinsicService, + chainRegistry: ChainRegistry, + accountRepository: AccountRepository, + walletRepository: WalletRepository, + sorablockexplorer: BlockExplorerManager, + poolDao: PoolDao, + appDataBase: AppDatabase + ): PoolsRepository { + return PoolsRepositoryImpl( + extrinsicService, + chainRegistry, + accountRepository, + walletRepository, + sorablockexplorer, + poolDao, + appDataBase + ) + } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 4cfd676716..dd01d4e581 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -9,39 +9,37 @@ import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.liquiditypools.data.PoolsRepository import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor -import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.mapNotNull class PoolsInteractorImpl( - private val polkaswapRepository: PolkaswapRepository, + private val poolsRepository: PoolsRepository, private val accountRepository: AccountRepository, - private val polkaswapInteractor: PolkaswapInteractor, private val chainRegistry: ChainRegistry, private val keypairProvider: KeypairProvider, ) : PoolsInteractor { - override val poolsChainId = soraTestChainId - override suspend fun getBasicPools(chainId: ChainId): List { - return polkaswapRepository.getBasicPools(chainId) + override val poolsChainId = poolsRepository.poolsChainId + + override suspend fun getBasicPools(): List { + return poolsRepository.getBasicPools(poolsChainId) } // override fun subscribePoolsCache(): Flow> { -// return polkaswapRepository.subscribePools() +// return poolsRepository.subscribePools() // } override fun subscribePoolsCacheOfAccount(address: String): Flow> { - return polkaswapRepository.subscribePools(address) + return poolsRepository.subscribePools(address) } private val soraPoolsAddressFlow = flowOf { @@ -54,14 +52,14 @@ class PoolsInteractorImpl( override fun subscribePoolsCacheCurrentAccount(): Flow> { return soraPoolsAddressFlow.flatMapLatest { address -> - polkaswapRepository.subscribePools(address) + poolsRepository.subscribePools(address) } } - override suspend fun getPoolData(chainId: ChainId, baseTokenId: String, targetTokenId: String): Flow { - val address = accountRepository.getSelectedAccount(chainId).address - return polkaswapRepository.subscribePool(address, baseTokenId, targetTokenId) + override suspend fun getPoolData(baseTokenId: String, targetTokenId: String): Flow { + val address = accountRepository.getSelectedAccount(poolsChainId).address + return poolsRepository.subscribePool(address, baseTokenId, targetTokenId) } // override suspend fun getPoolCacheOfCurAccount( @@ -72,7 +70,7 @@ class PoolsInteractorImpl( // val chainId = polkaswapInteractor.polkaswapChainId // val chain = chainRegistry.getChain(chainId) // val address = wallet.address(chain) -// return polkaswapRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) +// return poolsRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) // } override suspend fun getUserPoolData( @@ -81,8 +79,8 @@ class PoolsInteractorImpl( baseTokenId: String, tokenId: ByteArray ): PoolDataDto? { -// return polkaswapRepository.getPoolOfAccount(address, baseTokenId, tokenId.toHexString(true), polkaswapInteractor.polkaswapChainId) - return polkaswapRepository.getUserPoolData(chainId, address, baseTokenId, tokenId) +// return poolsRepository.getPoolOfAccount(address, baseTokenId, tokenId.toHexString(true), polkaswapInteractor.polkaswapChainId) + return poolsRepository.getUserPoolData(chainId, address, baseTokenId, tokenId) } @@ -97,7 +95,7 @@ class PoolsInteractorImpl( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal? { - return polkaswapRepository.calcAddLiquidityNetworkFee( + return poolsRepository.calcAddLiquidityNetworkFee( chainId, address, tokenBase, @@ -111,24 +109,23 @@ class PoolsInteractorImpl( } override suspend fun calcRemoveLiquidityNetworkFee( - chainId: ChainId, tokenBase: Asset, tokenTarget: Asset, ): BigDecimal? { - return polkaswapRepository.calcRemoveLiquidityNetworkFee( - chainId, + return poolsRepository.calcRemoveLiquidityNetworkFee( + poolsChainId, tokenBase, tokenTarget ) } override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? = - polkaswapInteractor.getPoolStrategicBonusAPY(reserveAccountOfPool) + poolsRepository.getPoolStrategicBonusAPY(reserveAccountOfPool) - override suspend fun isPairEnabled(chainId: ChainId, baseTokenId: String, targetTokenId: String): Boolean { - val dexId = polkaswapRepository.getPoolBaseTokenDexId(chainId, baseTokenId) - return polkaswapRepository.isPairAvailable( - chainId, + override suspend fun isPairEnabled(baseTokenId: String, targetTokenId: String): Boolean { + val dexId = poolsRepository.getPoolBaseTokenDexId(poolsChainId, baseTokenId) + return poolsRepository.isPairAvailable( + poolsChainId, baseTokenId, targetTokenId, dexId @@ -146,7 +143,7 @@ class PoolsInteractorImpl( ): String { val address = accountRepository.getSelectedAccount(chainId).address - val status = polkaswapRepository.observeRemoveLiquidity( + val status = poolsRepository.observeRemoveLiquidity( chainId, tokenBase, tokenTarget, @@ -191,7 +188,7 @@ class PoolsInteractorImpl( val nonce = secrets[KeyPairSchema.Nonce] val keypair = Keypair(public, private, nonce) - val status = polkaswapRepository.observeAddLiquidity( + val status = poolsRepository.observeAddLiquidity( chainId, address, keypair, @@ -227,8 +224,8 @@ class PoolsInteractorImpl( override suspend fun updatePools(chainId: ChainId) { val address = accountRepository.getSelectedAccount(chainId).address - polkaswapRepository.updateAccountPools(chainId, address) + poolsRepository.updateAccountPools(chainId, address) - polkaswapRepository.updateBasicPools(chainId) + poolsRepository.updateBasicPools(chainId) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 827e0c7730..082870e192 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -42,28 +42,27 @@ class InternalPoolsRouterImpl( } } - override fun openAllPoolsScreen(chainId: ChainId) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen(chainId)) + override fun openAllPoolsScreen() { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.AllPoolsScreen()) } - override fun openDetailsPoolScreen(chainId: ChainId, ids: StringPair) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(chainId, ids)) + override fun openDetailsPoolScreen(ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.PoolDetailsScreen(ids)) } - override fun openAddLiquidityScreen(chainId: ChainId, ids: StringPair) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(chainId, ids)) + override fun openAddLiquidityScreen(ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(ids)) } - override fun openAddLiquidityConfirmScreen(chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(chainId, ids, amountBase, amountTarget, apy)) + override fun openAddLiquidityConfirmScreen(ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(ids, amountBase, amountTarget, apy)) } - override fun openRemoveLiquidityScreen(chainId: ChainId, ids: StringPair) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen(chainId, ids)) + override fun openRemoveLiquidityScreen(ids: StringPair) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen(ids)) } override fun openRemoveLiquidityConfirmScreen( - chainId: ChainId, ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, @@ -71,11 +70,11 @@ class InternalPoolsRouterImpl( secondAmountMin: BigDecimal, desired: BigDecimal ) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(chainId, ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired)) + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen(ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired)) } - override fun openPoolListScreen(chainId: ChainId, isUserPools: Boolean) { - mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen(chainId, isUserPools)) + override fun openPoolListScreen(isUserPools: Boolean) { + mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.ListPoolsScreen(isUserPools)) } override fun openErrorsScreen(title: String?, message: String) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 9e23f8cbb8..bbd0c07f80 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -116,7 +116,7 @@ class PoolsFlowViewModel @Inject constructor( ?: error("Can't find ${PoolsFlowFragment.POLKASWAP_CHAIN_ID} in arguments") init { - internalPoolsRouter.openAllPoolsScreen(polkaswapChainId) + internalPoolsRouter.openAllPoolsScreen() launch { // poolsInteractor.updateApy() @@ -182,7 +182,7 @@ class PoolsFlowViewModel @Inject constructor( } override fun onPoolClicked(pair: StringPair) { - internalPoolsRouter.openDetailsPoolScreen(polkaswapChainId, pair) + internalPoolsRouter.openDetailsPoolScreen(pair) } fun onNavigationClick() { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 4cb75c5bb4..210d43b94a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -5,6 +5,7 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState @@ -17,12 +18,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -51,8 +51,8 @@ class AllPoolsPresenter @Inject constructor( // it.map(BasicPoolData::toListItemState) // } - val chainFlow = screenArgsFlow.map { screenArgs -> - chainsRepository.getChain(screenArgs.chainId) + private val chainFlow = flowOf { + chainsRepository.getChain(poolsInteractor.poolsChainId) } @OptIn(ExperimentalCoroutinesApi::class) @@ -119,12 +119,10 @@ class AllPoolsPresenter @Inject constructor( override fun onPoolClicked(pair: StringPair) { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return - internalPoolsRouter.openDetailsPoolScreen(chainId, pair) + internalPoolsRouter.openDetailsPoolScreen(pair) } override fun onMoreClick(isUserPools: Boolean) { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return - internalPoolsRouter.openPoolListScreen(chainId, isUserPools) + internalPoolsRouter.openPoolListScreen(isUserPools) } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index a569642a68..6a631f6494 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -11,6 +11,7 @@ import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -103,7 +104,7 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val assetsInPoolFlow = screenArgsFlow.distinctUntilChanged().flatMapLatest { screenArgs -> val ids = screenArgs.ids - val chainId = screenArgs.chainId + val chainId = poolsInteractor.poolsChainId println("!!! assetsInPoolFlow ADD ids = $ids") val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { @@ -135,7 +136,6 @@ class LiquidityAddPresenter @Inject constructor( screenArgsFlow.flatMapLatest { screenargs -> println("!!! AddPresenter screenArgsFlow = $screenargs") poolsInteractor.getPoolData( - chainId = screenargs.chainId, baseTokenId = screenargs.ids.first, targetTokenId = screenargs.ids.second ).onEach { @@ -267,7 +267,7 @@ class LiquidityAddPresenter @Inject constructor( tokenAmount = scaledTargetAmount, fiatAmount = scaledTargetAmount.applyFiatRate(tokenTarget?.fiatRate)?.formatFiat(tokenTarget?.fiatSymbol), - ) + ) ) amountTarget = scaledTargetAmount } else { @@ -303,7 +303,6 @@ class LiquidityAddPresenter @Inject constructor( val liquidity = screenArgsFlow.flatMapLatest { screenArgs -> poolsInteractor.getPoolData( - chainId = screenArgs.chainId, baseTokenId = screenArgs.ids.first, targetTokenId = screenArgs.ids.second ) @@ -332,7 +331,6 @@ class LiquidityAddPresenter @Inject constructor( screenArgsFlow.map { screenArgs -> val (baseTokenId, targetTokenId) = screenArgs.ids poolsInteractor.isPairEnabled( - screenArgs.chainId, baseTokenId, targetTokenId ) @@ -361,24 +359,23 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private val feeInfoViewStateFlow: Flow = - screenArgsFlow.map { screenArgs -> - val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) - screenArgs.chainId to utilityAssetId - }.flatMapLatest { (chainId, utilityAssetId) -> - combine( - networkFeeFlow, - walletInteractor.assetFlow(chainId, utilityAssetId) - ) { networkFee, utilityAsset -> - val tokenSymbol = utilityAsset.token.configuration.symbol - val tokenFiatRate = utilityAsset.token.fiatRate - val tokenFiatSymbol = utilityAsset.token.fiatSymbol - - FeeInfoViewState( - feeAmount = networkFee.formatCryptoDetail(tokenSymbol), - feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), - ) + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } } - } private suspend fun getLiquidityNetworkFee( tokenBase: Asset, @@ -389,7 +386,7 @@ class LiquidityAddPresenter @Inject constructor( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + val chainId = poolsInteractor.poolsChainId val soraChain = walletInteractor.getChain(chainId) val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() @@ -418,8 +415,7 @@ class LiquidityAddPresenter @Inject constructor( println("!!! should setButtonLoading(true)") coroutinesStore.uiScope.launch { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - + val chainId = poolsInteractor.poolsChainId val utilityAssetId = requireNotNull(chainsRepository.getChain(chainId).utilityAsset?.id) val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total val feeAmount = networkFeeFlow.firstOrNull().orZero() @@ -448,7 +444,7 @@ class LiquidityAddPresenter @Inject constructor( } val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openAddLiquidityConfirmScreen(chainId, ids, amountBase, amountTarget, stateFlow.value.apy.orEmpty()) + internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, stateFlow.value.apy.orEmpty()) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 869d515c5d..fe9c745889 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -7,6 +7,7 @@ import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -58,7 +59,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids - val chainId = screenArgs.chainId + val chainId = poolsInteractor.poolsChainId val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { it.asset.token.configuration.currencyId == ids.first @@ -84,7 +85,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( private val isPoolPairEnabled = screenArgsFlow.map { screenArgs -> poolsInteractor.isPairEnabled( - chainId = screenArgs.chainId, baseTokenId = screenArgs.ids.first, targetTokenId = screenArgs.ids.second ) @@ -151,13 +151,12 @@ class LiquidityAddConfirmPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private val feeInfoViewStateFlow: Flow = - screenArgsFlow.map { screenArgs -> - val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) - screenArgs.chainId to utilityAssetId - }.flatMapLatest { (chainId, utilityAssetId) -> + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> combine( networkFeeFlow, - walletInteractor.assetFlow(chainId, utilityAssetId) + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) ) { networkFee, utilityAsset -> val tokenSymbol = utilityAsset.token.configuration.symbol val tokenFiatRate = utilityAsset.token.fiatRate @@ -179,7 +178,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + val chainId = poolsInteractor.poolsChainId val soraChain = walletInteractor.getChain(chainId) val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val result = poolsInteractor.calcAddLiquidityNetworkFee( @@ -199,7 +198,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( override fun onConfirmClick() { setButtonLoading(true) coroutinesStore.ioScope.launch { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + val chainId = poolsInteractor.poolsChainId val tokenBase = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch val tokenTarget = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch val amountBase = screenArgsFlow.firstOrNull()?.amountBase.orZero() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 700680e01f..42cdae7c3e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -10,6 +10,7 @@ import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -106,7 +107,7 @@ class LiquidityRemovePresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids - val chainId = screenArgs.chainId + val chainId = poolsInteractor.poolsChainId println("!!! assetsInPoolFlow ids = $ids") val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { @@ -135,7 +136,6 @@ class LiquidityRemovePresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val poolDataFlow = screenArgsFlow.flatMapLatest { poolsInteractor.getPoolData( - chainId = it.chainId, baseTokenId = it.ids.first, targetTokenId = it.ids.second ) @@ -163,8 +163,8 @@ class LiquidityRemovePresenter @Inject constructor( val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@map null val (token1Id, token2Id) = ids - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId - val result = if (poolDataLocal != null && chainId != null) { + val chainId = poolsInteractor.poolsChainId + val result = if (poolDataLocal != null) { val maxPercent = demeterFarmingInteractor.getFarmedPools(chainId)?.filter { pool -> pool.tokenBase.token.configuration.currencyId == token1Id && pool.tokenTarget.token.configuration.currencyId == token2Id @@ -424,22 +424,20 @@ class LiquidityRemovePresenter @Inject constructor( } @OptIn(ExperimentalCoroutinesApi::class) - val utilityAssetFlow = screenArgsFlow.map { screenArgs -> - val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) - screenArgs.chainId to utilityAssetId - }.flatMapLatest { (chainId, utilityAssetId) -> - walletInteractor.assetFlow(chainId, utilityAssetId) + val utilityAssetFlow = flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) } @OptIn(ExperimentalCoroutinesApi::class) private val feeInfoViewStateFlow: Flow = - screenArgsFlow.map { screenArgs -> - val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) - screenArgs.chainId to utilityAssetId - }.flatMapLatest { (chainId, utilityAssetId) -> + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> combine( networkFeeFlow, - walletInteractor.assetFlow(chainId, utilityAssetId) + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) ) { networkFee, utilityAsset -> val tokenSymbol = utilityAsset.token.configuration.symbol val tokenFiatRate = utilityAsset.token.fiatRate @@ -456,10 +454,7 @@ class LiquidityRemovePresenter @Inject constructor( tokenBase: Asset, tokenTarget: Asset, ): BigDecimal { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO - val result = poolsInteractor.calcRemoveLiquidityNetworkFee( - chainId, tokenBase, tokenTarget, ) @@ -476,8 +471,6 @@ class LiquidityRemovePresenter @Inject constructor( setButtonLoading(true) coroutinesStore.uiScope.launch { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - val utilityAmount = utilityAssetFlow.firstOrNull()?.transferable ?: return@launch val feeAmount = networkFeeFlow.firstOrNull().orZero() @@ -531,7 +524,7 @@ class LiquidityRemovePresenter @Inject constructor( val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openRemoveLiquidityConfirmScreen(chainId, ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired) + internalPoolsRouter.openRemoveLiquidityConfirmScreen(ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index ff36c2dc03..5de4d0a757 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -7,6 +7,7 @@ import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat @@ -58,7 +59,7 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids - val chainId = screenArgs.chainId + val chainId = poolsInteractor.poolsChainId val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { it.asset.token.configuration.currencyId == ids.first @@ -84,7 +85,6 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( private val isPoolPairEnabled = screenArgsFlow.map { screenArgs -> poolsInteractor.isPairEnabled( - chainId = screenArgs.chainId, baseTokenId = screenArgs.ids.first, targetTokenId = screenArgs.ids.second ) @@ -139,13 +139,12 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private val feeInfoViewStateFlow: Flow = - screenArgsFlow.map { screenArgs -> - val utilityAssetId = requireNotNull(chainsRepository.getChain(screenArgs.chainId).utilityAsset?.id) - screenArgs.chainId to utilityAssetId - }.flatMapLatest { (chainId, utilityAssetId) -> + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> combine( networkFeeFlow, - walletInteractor.assetFlow(chainId, utilityAssetId) + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) ) { networkFee, utilityAsset -> val tokenSymbol = utilityAsset.token.configuration.symbol val tokenFiatRate = utilityAsset.token.fiatRate @@ -167,7 +166,7 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( pairPresented: Boolean, slippageTolerance: Double ): BigDecimal { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return BigDecimal.ZERO + val chainId = poolsInteractor.poolsChainId val soraChain = walletInteractor.getChain(chainId) val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() val result = poolsInteractor.calcAddLiquidityNetworkFee( @@ -192,7 +191,7 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( val desired = screenArgsFlow.replayCache.firstOrNull()?.desired ?: return@launch val networkFee = networkFeeFlow.firstOrNull() ?: return@launch - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch + val chainId = poolsInteractor.poolsChainId val tokenBase = tokensInPoolFlow.firstOrNull()?.first?.configuration ?: return@launch val tokenTarget = tokensInPoolFlow.firstOrNull()?.second?.configuration ?: return@launch diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index af0b3c648d..eb8057cc07 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -14,7 +14,6 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope @@ -58,7 +57,7 @@ class PoolDetailsPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { screenArgsFlow.flatMapLatest { - observePoolDetails(it.chainId, it.ids).onEach { + observePoolDetails(it.ids).onEach { stateFlow.value = it.mapToState() } // requestPoolDetails(it.ids)?.let { @@ -71,16 +70,14 @@ class PoolDetailsPresenter @Inject constructor( override fun onSupplyLiquidityClick() { coroutinesStore.ioScope.launch { val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - internalPoolsRouter.openAddLiquidityScreen(chainId, ids) + internalPoolsRouter.openAddLiquidityScreen(ids) } } override fun onRemoveLiquidityClick() { coroutinesStore.ioScope.launch { val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return@launch - internalPoolsRouter.openRemoveLiquidityScreen(chainId, ids) + internalPoolsRouter.openRemoveLiquidityScreen(ids) } } @@ -89,13 +86,13 @@ class PoolDetailsPresenter @Inject constructor( internalPoolsRouter.openInfoScreen(itemId) } - suspend fun observePoolDetails(chainId: ChainId, ids: StringPair): Flow { + suspend fun observePoolDetails(ids: StringPair): Flow { val (baseTokenId, targetTokenId) = ids - return poolsInteractor.getPoolData(chainId, baseTokenId, targetTokenId) + return poolsInteractor.getPoolData(baseTokenId, targetTokenId) } suspend fun requestPoolDetails(ids: StringPair): PoolDetailsState? { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return null + val chainId = poolsInteractor.poolsChainId val soraChain = accountInteractor.getChain(chainId) val address = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 17104cc434..bfb11626da 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -9,7 +9,6 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor @@ -82,8 +81,7 @@ class PoolListPresenter @Inject constructor( } override fun onPoolClicked(pair: StringPair) { - val chainId = screenArgsFlow.replayCache.firstOrNull()?.chainId ?: return - internalPoolsRouter.openDetailsPoolScreen(chainId, pair) + internalPoolsRouter.openDetailsPoolScreen(pair) } override fun onAssetSearchEntered(value: String) { diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt index bed8608d49..86b301f3f4 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PolkaswapRepository.kt @@ -1,15 +1,10 @@ package jp.co.soramitsu.polkaswap.api.data -import java.math.BigDecimal import java.math.BigInteger -import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import kotlinx.coroutines.flow.Flow interface PolkaswapRepository { @@ -59,66 +54,4 @@ interface PolkaswapRepository { desired: WithDesired ): Result - suspend fun getBasicPools(chainId: ChainId): List - - suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? - - suspend fun getUserPoolData( - chainId: ChainId, - address: String, - baseTokenId: String, - targetTokenId: ByteArray - ): PoolDataDto? - - suspend fun calcAddLiquidityNetworkFee( - chainId: ChainId, - address: String, - tokenBase: Asset, - tokenTarget: Asset, - tokenBaseAmount: BigDecimal, - tokenTargetAmount: BigDecimal, - pairEnabled: Boolean, - pairPresented: Boolean, - slippageTolerance: Double - ): BigDecimal? - - suspend fun calcRemoveLiquidityNetworkFee( - chainId: ChainId, - tokenBase: Asset, - tokenTarget: Asset, - ): BigDecimal? - - suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int - - fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? - - suspend fun observeRemoveLiquidity( - chainId: ChainId, - tokenBase: Asset, - tokenTarget: Asset, - markerAssetDesired: BigDecimal, - firstAmountMin: BigDecimal, - secondAmountMin: BigDecimal - ): Result? - - suspend fun observeAddLiquidity( - chainId: ChainId, - address: String, - keypair: Keypair, - tokenBase: Asset, - tokenTarget: Asset, - amountBase: BigDecimal, - amountTarget: BigDecimal, - pairEnabled: Boolean, - pairPresented: Boolean, - slippageTolerance: Double - ): Result? - - suspend fun updateAccountPools(chainId: ChainId, address: String) - suspend fun updateBasicPools(chainId: ChainId) - -// fun subscribePools(): Flow> - fun subscribePools(address: String): Flow> - fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow - } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt index 49560b2f8c..f5590b7e7c 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/PolkaswapInteractor.kt @@ -63,6 +63,4 @@ interface PolkaswapInteractor { suspend fun getAvailableDexesForPair(tokenFromId: String, tokenToId: String, dexes: List): List fun observeHasReadDisclaimer(): Flow -// suspend fun updatePoolsSbApy() - fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt index 48a1f379d1..aa51cb7730 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/PolkaswapRepositoryImpl.kt @@ -1,112 +1,48 @@ package jp.co.soramitsu.polkaswap.impl.data -import androidx.room.Transaction -import androidx.room.withTransaction -import java.math.BigDecimal import java.math.BigInteger import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository -import jp.co.soramitsu.account.api.domain.model.accountId -import jp.co.soramitsu.androidfoundation.format.addHexPrefix -import jp.co.soramitsu.androidfoundation.format.mapBalance -import jp.co.soramitsu.androidfoundation.format.safeCast import jp.co.soramitsu.common.data.network.config.PolkaswapRemoteConfig import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher -import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.dexManager -import jp.co.soramitsu.common.utils.flowOf -import jp.co.soramitsu.common.utils.fromHex -import jp.co.soramitsu.common.utils.mapList import jp.co.soramitsu.common.utils.poolTBC import jp.co.soramitsu.common.utils.poolXYK import jp.co.soramitsu.common.utils.u32ArgumentFromStorageKey import jp.co.soramitsu.core.extrinsic.ExtrinsicService -import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse -import jp.co.soramitsu.core.utils.utilityAsset -import jp.co.soramitsu.coredb.AppDatabase -import jp.co.soramitsu.coredb.dao.PoolDao -import jp.co.soramitsu.coredb.model.BasicPoolLocal -import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal -import jp.co.soramitsu.coredb.model.UserPoolJoinedLocalNullable -import jp.co.soramitsu.coredb.model.UserPoolLocal import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData import jp.co.soramitsu.polkaswap.api.models.Market import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.polkaswap.api.models.backStrings import jp.co.soramitsu.polkaswap.api.models.toFilters import jp.co.soramitsu.polkaswap.api.models.toMarkets -import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.bindings.bindDexInfos -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.liquidityAdd -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.register -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.removeLiquidity import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.swap -import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas -import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.network.subscriptionFlowCatching import jp.co.soramitsu.runtime.storage.source.StorageDataSource -import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import jp.co.soramitsu.shared_utils.extensions.fromHex -import jp.co.soramitsu.shared_utils.extensions.toHexString -import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct -import jp.co.soramitsu.shared_utils.runtime.definitions.types.fromHex -import jp.co.soramitsu.shared_utils.runtime.metadata.module import jp.co.soramitsu.shared_utils.runtime.metadata.storage import jp.co.soramitsu.shared_utils.runtime.metadata.storageKey -import jp.co.soramitsu.shared_utils.scale.Schema -import jp.co.soramitsu.shared_utils.scale.dataType.uint32 -import jp.co.soramitsu.shared_utils.scale.sizedByteArray -import jp.co.soramitsu.shared_utils.scale.uint128 -import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId import jp.co.soramitsu.shared_utils.wsrpc.exception.RpcException import jp.co.soramitsu.shared_utils.wsrpc.executeAsync import jp.co.soramitsu.shared_utils.wsrpc.mappers.nonNull import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojoList -import jp.co.soramitsu.shared_utils.wsrpc.mappers.scale import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest -import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest -import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest -import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange -import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository -import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks -import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach class PolkaswapRepositoryImpl @Inject constructor( private val remoteConfigFetcher: RemoteConfigFetcher, private val remoteStorage: StorageDataSource, private val extrinsicService: ExtrinsicService, private val chainRegistry: ChainRegistry, - private val rpcCalls: RpcCalls, private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository, - private val blockExplorerManager: BlockExplorerManager, - private val poolDao: PoolDao, - private val db: AppDatabase, ) : PolkaswapRepository { override suspend fun getAvailableDexes(chainId: ChainId): List { @@ -271,1012 +207,4 @@ class PolkaswapRepositoryImpl @Inject constructor( listOf() } } - - fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } - fun String.mapAssetId() = this.fromHex().mapAssetId() - fun String.mapCodeToken() = Struct.Instance( - mapOf("code" to this.mapAssetId()) - ) - - fun RuntimeSnapshot.reservesKeyToken(baseTokenId: String): String = - this.metadata.module(Modules.POOL_XYK) - .storage("Reserves") - .storageKey( - this, - baseTokenId.mapCodeToken(), - ) - - suspend fun getStorageHex(chainId: ChainId, storageKey: String): String? = - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = pojo(), - ).result - - suspend fun getStateKeys(chainId: ChainId, partialKey: String): List = - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = StateKeys(listOf(partialKey)), - mapper = pojoList(), - ).result ?: emptyList() - - class StateKeys(params: List) : RuntimeRequest("state_getKeys", params) - - fun ByteArray.mapCodeToken() = Struct.Instance( - mapOf("code" to this.mapAssetId()) - ) - - object PoolPropertiesResponse : Schema() { - val first by sizedByteArray(32) - val second by sizedByteArray(32) - } - - suspend fun getPoolReserveAccount( - chainId: ChainId, - baseTokenId: String, - tokenId: ByteArray - ): ByteArray? { - val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) - val storageKey = runtimeOrNull?.metadata - ?.module(Modules.POOL_XYK) - ?.storage("Properties")?.storageKey( - runtimeOrNull, - baseTokenId.mapCodeToken(), - tokenId.mapCodeToken(), - ) - ?: return null - - return chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = scale(PoolPropertiesResponse), - ) - .result - ?.let { storage -> - storage[storage.schema.first] - } - } - - fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() - - object TotalIssuance : Schema() { - val totalIssuance by uint128() - } - - suspend fun getPoolTotalIssuances( - chainId: ChainId, - reservesAccountId: ByteArray, - ): BigInteger? { - val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) - val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) - ?.storage("TotalIssuances") - ?.storageKey(runtimeOrNull, reservesAccountId) - ?: return null - return chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = scale(TotalIssuance), - ) - .result - ?.let { storage -> - storage[storage.schema.totalIssuance] - } - } - - override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { - val tempApy = blockExplorerManager.getTempApy(reserveAccountOfPool) -// println("!!! blockExplorerManager getPoolStrategicBonusAPY for address $reserveAccountOfPool = $tempApy") - return tempApy - } - -// private suspend fun updatePoolsSbApy() { -// println("!!! call blockExplorerManager.updatePoolsSbApy()") -// blockExplorerManager.updatePoolsSbApy() -// } -override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? { - val poolLocal = poolDao.getBasicPool(baseTokenId, targetTokenId) ?: return null - - val soraChain = chainRegistry.getChain(chainId) - val wallet = accountRepository.getSelectedMetaAccount() - val accountId = wallet.accountId(soraChain) - val soraAssets = soraChain.assets.mapNotNull { chainAsset -> - accountId?.let { - walletRepository.getAsset( - metaId = wallet.id, - accountId = accountId, - chainAsset = chainAsset, - minSupportedVersion = null - ) - } - } - - val baseAsset = soraAssets.firstOrNull { - it.token.configuration.currencyId == baseTokenId - } ?: return null - val targetAsset = soraAssets.firstOrNull { - it.token.configuration.currencyId == targetTokenId - } - - return BasicPoolData( - baseToken = baseAsset, - targetToken = targetAsset, - baseReserves = poolLocal.reserveBase, - targetReserves = poolLocal.reserveTarget, - totalIssuance = poolLocal.totalIssuance, - reserveAccount = poolLocal.reservesAccount, - sbapy = getPoolStrategicBonusAPY(poolLocal.reservesAccount) - ) -} - - override suspend fun getBasicPools(chainId: ChainId): List { - println("!!! getBasicPools() start") - val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) - val storage = runtimeOrNull?.metadata - ?.module(Modules.POOL_XYK) - ?.storage("Reserves") - - val list = mutableListOf() - - val soraChain = chainRegistry.getChain(chainId) - - val wallet = accountRepository.getSelectedMetaAccount() - - val accountId = wallet.accountId(soraChain) - val soraAssets = soraChain.assets.mapNotNull { chainAsset -> - accountId?.let { - walletRepository.getAsset( - metaId = wallet.id, - accountId = accountId, - chainAsset = chainAsset, - minSupportedVersion = null - ) - } - } - - println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") - - soraAssets.forEach { asset -> - val currencyId = asset.token.configuration.currencyId - val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } - key?.let { - getStateKeys(chainId, it).forEach { storageKey -> - val targetToken = storageKey.assetIdFromKey() - getStorageHex(chainId, storageKey)?.let { storageHex -> - storage?.type?.value - ?.fromHex(runtimeOrNull, storageHex) - ?.safeCast>()?.let { reserves -> - - val reserveAccount = getPoolReserveAccount( - chainId, - currencyId, - targetToken.fromHex() - ) - val total = reserveAccount?.let { - getPoolTotalIssuances(chainId, it) - }?.let { - mapBalance(it, asset.token.configuration.precision) - } - val targetAsset = soraAssets.firstOrNull { it.token.configuration.currencyId == targetToken } - val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" - - val element = BasicPoolData( - baseToken = asset, - targetToken = targetAsset, - baseReserves = mapBalance(reserves[0], asset.token.configuration.precision), - targetReserves = mapBalance(reserves[1], asset.token.configuration.precision), - totalIssuance = total ?: BigDecimal.ZERO, - reserveAccount = reserveAccountAddress, - sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) - ) - - println("!!! getBasicPools() list.add(BasicPoolData: $element") - list.add( - element - ) - } - } - } - } - } - - println("!!! getBasicPools() return list.size = ${list.size}") - - return list - } - - private fun subscribeAccountPoolProviders( - chainId: ChainId, - address: String, - reservesAccount: ByteArray, - ): Flow = flow { - val poolProvidersKey = - chainRegistry.getRuntimeOrNull(chainId)?.let { - it.metadata.module(Modules.POOL_XYK) - .storage("TotalIssuances") - .storageKey( - it, - reservesAccount, - address.toAccountId() - ) - } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") - val poolProvidersFlow = chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(poolProvidersKey), - "state_unsubscribeStorage", - ).map { - it.map { it.storageChange().getSingleChange().orEmpty() } - }.map { - it.getOrNull().orEmpty() - } - emitAll(poolProvidersFlow) - } - - override suspend fun getUserPoolData( - chainId: ChainId, - address: String, - baseTokenId: String, - targetTokenId: ByteArray - ): PoolDataDto? { - val reserves = getPairWithXorReserves(chainId, baseTokenId, targetTokenId) - val totalIssuanceAndProperties = - getPoolTotalIssuanceAndProperties(chainId, baseTokenId, targetTokenId, address) - - if (reserves == null || totalIssuanceAndProperties == null) { - return null - } - val reservesAccount = chainRegistry.getChain(chainId).addressOf(totalIssuanceAndProperties.third) - - return PoolDataDto( - baseTokenId, - targetTokenId.toHexString(true), - reserves.first, - reserves.second, - totalIssuanceAndProperties.first, - totalIssuanceAndProperties.second, - reservesAccount, - ) - } - - override suspend fun calcAddLiquidityNetworkFee( - chainId: ChainId, - address: String, - tokenBase: Asset, - tokenTarget: Asset, - tokenBaseAmount: BigDecimal, - tokenTargetAmount: BigDecimal, - pairEnabled: Boolean, - pairPresented: Boolean, - slippageTolerance: Double - ): BigDecimal? { - val amountFromMin = PolkaswapFormulas.calculateMinAmount(tokenBaseAmount, slippageTolerance) - val amountToMin = PolkaswapFormulas.calculateMinAmount(tokenTargetAmount, slippageTolerance) - val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) - val chain = chainRegistry.getChain(chainId) - - val fee = extrinsicService.estimateFee(chain) { - liquidityAdd( - dexId = dexId, - baseTokenId = tokenBase.currencyId, - targetTokenId = tokenTarget.currencyId, - pairPresented = pairPresented, - pairEnabled = pairEnabled, - tokenBaseAmount = tokenBase.planksFromAmount(tokenBaseAmount), - tokenTargetAmount = tokenTarget.planksFromAmount(tokenTargetAmount), - amountBaseMin = tokenBase.planksFromAmount(amountFromMin), - amountTargetMin = tokenTarget.planksFromAmount(amountToMin), - ) - } - - val feeToken = chain.utilityAsset - return feeToken?.amountFromPlanks(fee) - } - - override suspend fun calcRemoveLiquidityNetworkFee( - chainId: ChainId, - tokenBase: Asset, - tokenTarget: Asset, - ): BigDecimal? { - val chain = chainRegistry.getChain(chainId) - val baseTokenId = tokenBase.currencyId ?: return null - val targetTokenId = tokenTarget.currencyId ?: return null - - val fee = extrinsicService.estimateFee(chain) { - removeLiquidity( - dexId = getPoolBaseTokenDexId(chainId, baseTokenId), - outputAssetIdA = baseTokenId, - outputAssetIdB = targetTokenId, - markerAssetDesired = tokenBase.planksFromAmount(BigDecimal.ONE), - outputAMin = tokenBase.planksFromAmount(BigDecimal.ONE), - outputBMin = tokenTarget.planksFromAmount(BigDecimal.ONE), - ) - } - val feeToken = chain.utilityAsset - return feeToken?.amountFromPlanks(fee) - } - - override suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int { - return getPoolBaseTokens(chainId).first { - it.second == tokenId - }.first - } - - private suspend fun getPoolBaseTokens(chainId: ChainId): List> { - val runtimeSnapshot = chainRegistry.getRuntimeOrNull(chainId) - val metadataStorage = runtimeSnapshot?.metadata - ?.module("DEXManager") - ?.storage("DEXInfos") - val partialKey = metadataStorage - ?.storageKey() ?: error("getPoolBaseTokenDexId storageKey not supported") - - return chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = StateKeys(listOf(partialKey)), - mapper = pojoList().nonNull() - ).let { storageKeys -> - storageKeys.mapNotNull { storageKey -> - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = pojo().nonNull() - ).let { storage -> - val storageType = metadataStorage.type.value!! - val storageRawData = - storageType.fromHex(runtimeSnapshot, storage) - (storageRawData as? Struct.Instance)?.let { instance -> - instance.mapToToken("baseAssetId")?.let { token -> - storageKey.takeInt32() to token - } - } - } - } - } - } - - fun Struct.Instance.mapToToken(field: String) = - this.get(field)?.getTokenId()?.toHexString(true) - - fun String.takeInt32() = uint32.fromHex(this.takeLast(8)).toInt() - - private suspend fun getPoolTotalIssuanceAndProperties( - chainId: ChainId, - baseTokenId: String, - tokenId: ByteArray, - address: String - ): Triple? { - return getPoolReserveAccount(chainId, baseTokenId, tokenId)?.let { account -> - getPoolTotalIssuances( - chainId, - account - )?.let { - val provider = getPoolProviders( - chainId, - account, - address - ) - Triple(it, provider, account) - } - } - } - - object PoolProviders : Schema() { - val poolProviders by uint128() - } - - private suspend fun getPoolProviders( - chainId: ChainId, - reservesAccountId: ByteArray, - currentAddress: String - ): BigInteger { - val storageKey = - chainRegistry.getRuntimeOrNull(chainId)?.let { - it.metadata.module(Modules.POOL_XYK) - .storage("PoolProviders").storageKey( - it, - reservesAccountId, - currentAddress.toAccountId() - ) - } ?: return BigInteger.ZERO - return runCatching { - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = scale(PoolProviders), - ) - .let { storage -> - storage.result?.let { - it[it.schema.poolProviders] - } ?: BigInteger.ZERO - } - }.getOrElse { - it.printStackTrace() - throw it - } - } - - object ReservesResponse : Schema() { - val first by uint128() - val second by uint128() - } - - private suspend fun getPairWithXorReserves( - chainId: ChainId, - baseTokenId: String, - tokenId: ByteArray - ): Pair? { - val storageKey = - chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) - ?: return null - return try { - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = scale(ReservesResponse), - ) - .result - ?.let { storage -> - storage[storage.schema.first] to storage[storage.schema.second] - } - } catch (e: Exception) { - println("!!! getPairWithXorReserves error = ${e.message}") - - e.printStackTrace() - throw e - } - } - -// override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { -// return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { -// it.user.address == address -// } -// } - -// suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { -//// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) -//// val socketService = chainRegistry.getConnection(chainId).socketService -//// socketService.executeAsyncCatching() -// val tokensPair: List>>? = address?.let { -// getUserPoolsTokenIds(it) -// } -// -// val pools = mutableListOf() -// -// tokensPair?.forEach { (baseTokenId, tokensId) -> -// tokensId.mapNotNull { tokenId -> -// getUserPoolData(address, baseTokenId, tokenId) -// }.forEach pool@{ poolDataDto -> -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// val token = assets.firstOrNull { -// it.token.configuration.currencyId == poolDataDto.assetId -// } ?: return@pool -// val baseToken = assets.firstOrNull { -// it.token.configuration.currencyId == baseTokenId -// } ?: return@pool -// val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 -// val tokenPrecision = token?.token?.configuration?.precision ?: 0 -// -// val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) -// -// val basePooled = PolkaswapFormulas.calculatePooledValue( -// mapBalance( -// poolDataDto.reservesFirst, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// baseToken?.token?.configuration?.precision, -// ) -// val secondPooled = PolkaswapFormulas.calculatePooledValue( -// mapBalance( -// poolDataDto.reservesSecond, -// tokenPrecision -// ), -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// token?.token?.configuration?.precision, -// ) -// val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// ) -// val userPoolData = CommonUserPoolData( -// basic = BasicPoolData( -// baseToken = baseToken, -// targetToken = token, -// baseReserves = mapBalance( -// poolDataDto.reservesFirst, -// xorPrecision -// ), -// targetReserves = mapBalance( -// poolDataDto.reservesSecond, -// tokenPrecision -// ), -// totalIssuance = mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// reserveAccount = poolDataDto.reservesAccount, -// sbapy = apy, -// ), -// user = UserPoolData( -//// address = address, -// basePooled = basePooled, -// targetPooled = secondPooled, -// share, -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// ), -// ) -// pools.add(userPoolData) -// } -// } -// return pools -// } - - fun RuntimeSnapshot.reservesKey(baseTokenId: String, tokenId: ByteArray): String = - this.metadata.module(Modules.POOL_XYK) - .storage("Reserves") - .storageKey( - this, - baseTokenId.mapCodeToken(), - tokenId.mapCodeToken(), - ) - - @Suppress("UNCHECKED_CAST") - fun SubscriptionChange.storageChange(): SubscribeStorageResult { - val result = params.result as? Map<*, *> ?: throw IllegalArgumentException("${params.result} is not a valid storage result") - - val block = result["block"] as? String ?: throw IllegalArgumentException("$result is not a valid storage result") - val changes = result["changes"] as? List> ?: throw IllegalArgumentException("$result is not a valid storage result") - - return SubscribeStorageResult(block, changes) - } - - private fun subscribeToPoolData( - chainId: ChainId, - baseTokenId: String, - tokenId: ByteArray, - reservesAccount: ByteArray, - ): Flow = flow { - val reservesKey = - chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) - - val reservesFlow = reservesKey?.let { - chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(reservesKey), - "state_unsubscribeStorage", - ).map { - it.map { it.storageChange().getSingleChange().orEmpty() } - }.map { - it.getOrNull().orEmpty() - } - } ?: emptyFlow() - - val totalIssuanceKey = - chainRegistry.getRuntimeOrNull(chainId)?.let { - it.metadata.module(Modules.POOL_XYK) - .storage("TotalIssuances") - .storageKey(it, reservesAccount) - } - val totalIssuanceFlow = totalIssuanceKey?.let { - chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(totalIssuanceKey), - "state_unsubscribeStorage", - ).map { - it.getOrNull()?.storageChange()?.getSingleChange().orEmpty() - } - } ?: emptyFlow() - - val resultFlow = reservesFlow - .combine(totalIssuanceFlow) { reservesString, totalIssuanceString -> - (reservesString + totalIssuanceString).take(5) - } - - emitAll(resultFlow) - } - - // changes are in format [[storage key, value], [..], ..] - class SubscribeStorageResult(val block: String, val changes: List>) { - fun getSingleChange() = changes.first()[1] - } - - fun Struct.Instance.getTokenId() = get>("code") - ?.map { (it as BigInteger).toByte() } - ?.toByteArray() - - - suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { - val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) - return runCatching { - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = StateKeys(listOfNotNull(accountPoolsKey)), - mapper = pojoList().nonNull() - ) - }.onFailure { - println("!!! getUserPoolsTokenIdsKeys error: ${it.message}") - it.printStackTrace() - } - .getOrThrow() - } - - suspend fun getUserPoolsTokenIds( - chainId: ChainId, - address: String - ): List>> { - return runCatching { - val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) - storageKeys.map { storageKey -> - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = pojo().nonNull(), - ) - .let { storage -> - val storageType = - chainRegistry.getRuntimeOrNull(chainId)?.metadata?.module(Modules.POOL_XYK) - ?.storage("AccountPools")?.type?.value!! - val storageRawData = - storageType.fromHex(chainRegistry.getRuntimeOrNull(chainId)!!, storage) - val tokens: List = if (storageRawData is List<*>) { - storageRawData.filterIsInstance() - .mapNotNull { struct -> - struct.getTokenId() - } - } else { - emptyList() - } - storageKey.assetIdFromKey() to tokens - } - } - }.getOrThrow() - } - - override suspend fun observeRemoveLiquidity( - chainId: ChainId, - tokenBase: Asset, - tokenTarget: Asset, - markerAssetDesired: BigDecimal, - firstAmountMin: BigDecimal, - secondAmountMin: BigDecimal - ): Result? { - val soraChain = accountRepository.getChain(chainId) - val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId - val baseTokenId = tokenBase.currencyId ?: return null - val targetTokenId = tokenTarget.currencyId ?: return null - - return extrinsicService.submitExtrinsic( - chain = soraChain, - accountId = accountId - ) { - removeLiquidity( - dexId = getPoolBaseTokenDexId(chainId, baseTokenId), - outputAssetIdA = baseTokenId, - outputAssetIdB = targetTokenId, - markerAssetDesired = tokenBase.planksFromAmount(markerAssetDesired), - outputAMin = tokenBase.planksFromAmount(firstAmountMin), - outputBMin = tokenTarget.planksFromAmount(secondAmountMin), - ) - } - } - - override suspend fun observeAddLiquidity( - chainId: ChainId, - address: String, - keypair: Keypair, - tokenBase: Asset, - tokenTarget: Asset, - amountBase: BigDecimal, - amountTarget: BigDecimal, - pairEnabled: Boolean, - pairPresented: Boolean, - slippageTolerance: Double - ): Result? { - val amountFromMin = PolkaswapFormulas.calculateMinAmount(amountBase, slippageTolerance) - val amountToMin = PolkaswapFormulas.calculateMinAmount(amountTarget, slippageTolerance) - val dexId = getPoolBaseTokenDexId(chainId, tokenBase.currencyId) - val soraChain = accountRepository.getChain(chainId) - val accountId = accountRepository.getSelectedMetaAccount().substrateAccountId - - val baseTokenId = tokenBase.currencyId - val targetTokenId = tokenTarget.currencyId - if (baseTokenId == null || targetTokenId == null) return null - - return extrinsicService.submitExtrinsic( - chain = soraChain, - accountId = accountId, - useBatchAll = !pairPresented - ) { - if (!pairPresented) { - if (!pairEnabled) { - register( - dexId = dexId, - baseAssetId = baseTokenId, - targetAssetId = targetTokenId - ) - } - initializePool( - dexId = dexId, - baseAssetId = baseTokenId, - targetAssetId = targetTokenId - ) - } - - depositLiquidity( - dexId = dexId, - baseAssetId = baseTokenId, - targetAssetId = targetTokenId, - baseAssetAmount = mapBalance(amountBase, tokenBase.precision), - targetAssetAmount = mapBalance(amountTarget, tokenTarget.precision), - amountFromMin = mapBalance(amountFromMin, tokenBase.precision), - amountToMin = mapBalance(amountToMin, tokenTarget.precision) - ) - } - } - - override suspend fun updateAccountPools(chainId: ChainId, address: String) { - println("!!! call blockExplorerManager.updateAccountPools()") - blockExplorerManager.updatePoolsSbApy() - - val pools = mutableListOf() - - val assets = chainRegistry.getChain(chainId).assets - - val tokenIds = getUserPoolsTokenIds(chainId, address) - println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") - tokenIds.forEach { (baseTokenId, tokensId) -> - - val baseToken = assets.firstOrNull { - it.currencyId == baseTokenId - } ?: return@forEach - - val xorPrecision = baseToken.precision - - tokensId.mapNotNull { tokenId -> - getUserPoolData(chainId, address, baseTokenId, tokenId) - }.forEach pool@{ poolDataDto -> - val token = assets.firstOrNull { - it.currencyId == poolDataDto.assetId - } ?: return@pool - val tokenPrecision = token.precision - - val basicPoolLocal = BasicPoolLocal( - tokenIdBase = baseTokenId, - tokenIdTarget = poolDataDto.assetId, - reserveBase = mapBalance( - poolDataDto.reservesFirst, - xorPrecision - ), - reserveTarget = mapBalance( - poolDataDto.reservesSecond, - tokenPrecision - ), - totalIssuance = mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - reservesAccount = poolDataDto.reservesAccount, - ) - - val userPoolLocal = UserPoolLocal( - accountAddress = address, - userTokenIdBase = baseTokenId, - userTokenIdTarget = poolDataDto.assetId, - poolProvidersBalance = mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision - ) - ) - - pools.add( - UserPoolJoinedLocal( - userPoolLocal = userPoolLocal, - basicPoolLocal = basicPoolLocal, - ) - ) - } - } - - db.withTransaction { - println("!!! updateAccountPools: poolDao.clearTable(address)") - poolDao.clearTable(address) - println("!!! updateAccountPools: poolDao.insertBasicPools() size = ${pools.map { - it.basicPoolLocal - }.size}") - poolDao.insertBasicPools( - pools.map { - it.basicPoolLocal - } - ) - println("!!! updateAccountPools: poolDao.insertUSERPools() size = ${pools.map { - it.userPoolLocal - }.size}") - poolDao.insertUserPools( - pools.map { - it.userPoolLocal - } - ) - } - } - - override suspend fun updateBasicPools(chainId: ChainId) { - println("!!! pswapRepo updateBasicPools") - val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) - val storage = runtimeOrNull?.metadata - ?.module(Modules.POOL_XYK) - ?.storage("Reserves") - - val list = mutableListOf() - - val soraChain = chainRegistry.getChain(chainId) - val assets = soraChain.assets - - getPoolBaseTokens(chainId).forEach { (dexId, tokenId) -> - val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@forEach - val key = runtimeOrNull?.reservesKeyToken(tokenId) ?: return@forEach - - getStateKeys(chainId, key).forEach { storageKey -> - val targetToken = storageKey.assetIdFromKey() - getStorageHex(chainId, storageKey)?.let { storageHex -> - storage?.type?.value - ?.fromHex(runtimeOrNull, storageHex) - ?.safeCast>()?.let { reserves -> - - val reserveAccount = getPoolReserveAccount( - chainId, - tokenId, - targetToken.fromHex() - ) - - val total = reserveAccount?.let { - getPoolTotalIssuances(chainId, it) - }?.let { - mapBalance(it, asset.precision) - } - - val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" - - list.add( - BasicPoolLocal( - tokenIdBase = tokenId, - tokenIdTarget = targetToken, - reserveBase = mapBalance(reserves[0], asset.precision), - reserveTarget = mapBalance(reserves[1], asset.precision), - totalIssuance = total ?: BigDecimal.ZERO, - reservesAccount = reserveAccountAddress, - ) - ) - } - } - } - } - - val minus = poolDao.getBasicPools().filter { db -> - list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null - } - poolDao.deleteBasicPools(minus) - println("!!! pswapRepo insertBasicPools(list) size = ${list.size}") - poolDao.insertBasicPools(list) - } - val assetsFlow = accountRepository.selectedMetaAccountFlow() - .distinctUntilChanged { old, new -> old.id != new.id } - .flatMapLatest { meta -> - walletRepository.assetsFlow(meta) - }.mapList { it.asset } - - @OptIn(FlowPreview::class) - override fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow { - return combine( - poolDao.subscribePool(address, baseTokenId, targetTokenId), - assetsFlow - ) { pool, assets -> - mapPoolLocalToData(pool, assets) - } - .mapNotNull { it } - .debounce(500) - -// return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// mapPoolLocalToData(it, assets) -// }.debounce(500) - } - - @OptIn(FlowPreview::class) - override fun subscribePools(address: String): Flow> { - println("!!! repoImpl call subscribePools for address: $address") - - return combine( - poolDao.subscribeAllPools(address), - assetsFlow - ) { pools, assets -> - println("!!! repoImpl subscribePools pools: ${pools.size}") - val mapNotNull = pools.mapNotNull { poolLocal -> - mapPoolLocalToData(poolLocal, assets) - } - println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") - mapNotNull - } - .debounce(500) - -// return poolDao.subscribeAllPools(address).map { pools -> -// println("!!! repoImpl subscribePools pools: ${pools.size}") -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// val mapNotNull = pools.mapNotNull { poolLocal -> -// mapPoolLocalToData(poolLocal, assets) -// } -// println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") -// mapNotNull -// }.debounce(500) - .onEach { - println("!!! repoImpl .debounce(500) pools: ${it.size}") - } - } - - fun RuntimeSnapshot.accountPoolsKey(address: String): String = - this.metadata.module(Modules.POOL_XYK) - .storage("AccountPools") - .storageKey(this, address.toAccountId()) - - private fun mapPoolLocalToData( - poolLocal: UserPoolJoinedLocalNullable, - assets: List - ): CommonPoolData? { - val baseToken = assets.firstOrNull { - it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdBase - } ?: return null - val token = assets.firstOrNull { - it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdTarget - } ?: return null - - val basicPoolData = BasicPoolData( - baseToken = baseToken, - targetToken = token, - baseReserves = poolLocal.basicPoolLocal.reserveBase, - targetReserves = poolLocal.basicPoolLocal.reserveTarget, - totalIssuance = poolLocal.basicPoolLocal.totalIssuance, - reserveAccount = poolLocal.basicPoolLocal.reservesAccount, - sbapy = getPoolStrategicBonusAPY(poolLocal.basicPoolLocal.reservesAccount), - ) - - val userPoolData = poolLocal.userPoolLocal?.let { userPoolLocal -> - val basePooled = PolkaswapFormulas.calculatePooledValue( - poolLocal.basicPoolLocal.reserveBase, - userPoolLocal.poolProvidersBalance, - poolLocal.basicPoolLocal.totalIssuance, - baseToken.token.configuration.precision, - ) - val secondPooled = PolkaswapFormulas.calculatePooledValue( - poolLocal.basicPoolLocal.reserveTarget, - userPoolLocal.poolProvidersBalance, - poolLocal.basicPoolLocal.totalIssuance, - token.token.configuration.precision, - ) - val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( - userPoolLocal.poolProvidersBalance, - poolLocal.basicPoolLocal.totalIssuance, - ) - UserPoolData( - basePooled = basePooled, - targetPooled = secondPooled, - poolShare = share, - poolProvidersBalance = userPoolLocal.poolProvidersBalance, - ) - } - return CommonPoolData( - basic = basicPoolData, - user = userPoolData, - ) - } - } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 211949cf0f..19517782c9 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -53,24 +53,14 @@ class PolkaswapFeatureModule { @Named(REMOTE_STORAGE_SOURCE) remoteSource: StorageDataSource, extrinsicService: ExtrinsicService, chainRegistry: ChainRegistry, - rpcCalls: RpcCalls, accountRepository: AccountRepository, - walletRepository: WalletRepository, - sorablockexplorer: BlockExplorerManager, - poolDao: PoolDao, - appDataBase: AppDatabase ): PolkaswapRepository { return PolkaswapRepositoryImpl( remoteConfigFetcher, remoteSource, extrinsicService, chainRegistry, - rpcCalls, accountRepository, - walletRepository, - sorablockexplorer, - poolDao, - appDataBase ) } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt index 2fd73d3e60..6cf904310e 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/domain/PolkaswapInteractorImpl.kt @@ -269,13 +269,6 @@ class PolkaswapInteractorImpl @Inject constructor( }.filter { it.second }.map { it.first } } -// override suspend fun updatePoolsSbApy() { -// polkaswapRepository.updatePoolsSbApy() -// } - - override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String) = - polkaswapRepository.getPoolStrategicBonusAPY(reserveAccountOfPool) - override suspend fun swap( dexId: Int, inputAssetId: String, From b49a2c40198428803db7ec0eddf63408fa840544 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Thu, 1 Aug 2024 12:20:34 +0500 Subject: [PATCH 39/84] updates --- .../jp/co/soramitsu/app/root/navigation/Navigator.kt | 6 ++---- .../liquiditypools/impl/data/PoolsRepositoryImpl.kt | 7 ++++--- .../impl/presentation/PoolsFlowFragment.kt | 10 ---------- .../impl/presentation/PoolsFlowViewModel.kt | 5 +---- .../impl/presentation/allpools/AllPoolsScreen.kt | 4 ++-- .../polkaswap/api/presentation/PolkaswapRouter.kt | 2 +- .../api/sorablockexplorer/BlockExplorerManager.kt | 3 +++ .../presentation/swap_tokens/SwapTokensViewModel.kt | 7 +------ 8 files changed, 14 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 4520658d2d..420905d60e 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -73,7 +73,6 @@ import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.CustomContr import jp.co.soramitsu.crowdloan.impl.presentation.contribute.custom.model.CustomContributePayload import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.CrowdloanContributeFragment import jp.co.soramitsu.crowdloan.impl.presentation.contribute.select.parcel.ContributePayload -import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowFragment import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.nft.impl.presentation.NFTFlowFragment import jp.co.soramitsu.nft.navigation.NFTRouter @@ -1520,8 +1519,7 @@ class Navigator : navController?.navigate(R.id.scoreDetailsFragment, ScoreDetailsFragment.getBundle(metaId)) } - override fun openPools(chainId: ChainId) { - val bundle = PoolsFlowFragment.getBundle(chainId) - navController?.navigate(R.id.poolsFlowFragment, bundle) + override fun openPools() { + navController?.navigate(R.id.poolsFlowFragment) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index f34cccde22..f4040bc99d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -736,7 +736,7 @@ class PoolsRepositoryImpl @Inject constructor( ?.toByteArray() - suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { + private suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) return runCatching { chainRegistry.awaitConnection(chainId).socketService.executeAsync( @@ -750,7 +750,7 @@ class PoolsRepositoryImpl @Inject constructor( .getOrThrow() } - suspend fun getUserPoolsTokenIds( + private suspend fun getUserPoolsTokenIds( chainId: ChainId, address: String ): List>> { @@ -864,12 +864,13 @@ class PoolsRepositoryImpl @Inject constructor( } override suspend fun updateAccountPools(chainId: ChainId, address: String) { - println("!!! call blockExplorerManager.updateAccountPools()") + println("!!! call blockExplorerManager.updateAccountPools() chainId = $chainId") blockExplorerManager.updatePoolsSbApy() val pools = mutableListOf() val assets = chainRegistry.getChain(chainId).assets + println("!!! call blockExplorerManager.updateAccountPools() assets = ${assets.size}") val tokenIds = getUserPoolsTokenIds(chainId, address) println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 9be2e99df9..ff188d69ae 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -65,16 +65,6 @@ import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment() { - companion object { - const val POLKASWAP_CHAIN_ID = "polkaswapChainId" - - fun getBundle( - polkaswapChainId: ChainId, - ) = bundleOf( - POLKASWAP_CHAIN_ID to polkaswapChainId - ) - } - override val viewModel: PoolsFlowViewModel by viewModels() // Compose BackHandler does not work in DialogFragments, nor does BackPressedDispatcher diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index bbd0c07f80..4335228b03 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -112,15 +112,12 @@ class PoolsFlowViewModel @Inject constructor( replay = 1 ) - private val polkaswapChainId = savedStateHandle.get(PoolsFlowFragment.POLKASWAP_CHAIN_ID) - ?: error("Can't find ${PoolsFlowFragment.POLKASWAP_CHAIN_ID} in arguments") - init { internalPoolsRouter.openAllPoolsScreen() launch { // poolsInteractor.updateApy() - poolsInteractor.updatePools(polkaswapChainId) + poolsInteractor.updatePools(poolsInteractor.poolsChainId) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 46b14f1a7c..08de2a7fcb 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -67,7 +67,7 @@ fun AllPoolsScreen( ) { MarginVertical(margin = 8.dp) - if (state.isLoading) { + if (state.isLoading || (state.userPools.isEmpty() && state.allPools.isEmpty())) { ShimmerPoolList() } else { if (state.userPools.isNotEmpty()) { @@ -169,6 +169,7 @@ private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { color = white08, shape = CircleShape, ) + .clickable(onClick = onMoreClick) .padding(all = Dimens.x1) ) { Text( @@ -185,7 +186,6 @@ private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { modifier = Modifier .padding(start = 8.dp) .align(Alignment.CenterVertically) - .clickable(onClick = onMoreClick) ) } } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt index a88b1786b0..66a5fe4665 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/presentation/PolkaswapRouter.kt @@ -39,5 +39,5 @@ interface PolkaswapRouter { fun openWebViewer(title: String, url: String) - fun openPools(chainId: ChainId) + fun openPools() } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt index 586eccb337..7671ee2523 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt @@ -27,6 +27,9 @@ class BlockExplorerManager @Inject constructor( tempApy.clear() tempApy.addAll(response) println("!!! call blockExplorerManager.updatePoolsSbApy() result updated") + }.onFailure { + println("!!! updateSbApyInternal error% ${it.message}") + it.printStackTrace() } } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt index f58cfb971c..1594bfb078 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt @@ -11,7 +11,6 @@ import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode import javax.inject.Inject -import jp.co.soramitsu.common.BuildConfig import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.base.errors.ValidationException import jp.co.soramitsu.common.compose.component.AmountInputViewState @@ -34,7 +33,6 @@ import jp.co.soramitsu.common.validation.NotEnoughResultedAmountToPayFeeExceptio import jp.co.soramitsu.common.validation.SpendInsufficientBalanceException import jp.co.soramitsu.common.validation.UnableToPayFeeException import jp.co.soramitsu.common.validation.WaitForFeeCalculationException -import jp.co.soramitsu.core.models.ChainId import jp.co.soramitsu.feature_polkaswap_impl.R import jp.co.soramitsu.polkaswap.api.domain.InsufficientLiquidityException import jp.co.soramitsu.polkaswap.api.domain.PathUnavailableException @@ -48,8 +46,6 @@ import jp.co.soramitsu.polkaswap.api.presentation.models.SwapDetailsViewState import jp.co.soramitsu.polkaswap.api.presentation.models.TransactionSettingsModel import jp.co.soramitsu.polkaswap.api.presentation.models.detailsToViewState import jp.co.soramitsu.polkaswap.impl.presentation.transaction_settings.TransactionSettingsFragment -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId import jp.co.soramitsu.wallet.api.presentation.WalletRouter import jp.co.soramitsu.wallet.impl.domain.interfaces.QuickInputsUseCase import jp.co.soramitsu.wallet.impl.domain.model.Asset @@ -732,7 +728,6 @@ class SwapTokensViewModel @Inject constructor( } override fun onPoolsClick() { - val polkaswapChainId = if (BuildConfig.DEBUG) soraTestChainId else soraMainChainId - polkaswapRouter.openPools(polkaswapChainId) + polkaswapRouter.openPools() } } From 189014c48bcaaa8cf06349c7f80f049e5c02037e Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 7 Aug 2024 11:29:20 +0500 Subject: [PATCH 40/84] FLW-4819 Wrong name of field FLW-4820 Wrong name of page --- common/src/main/res/values/strings.xml | 2 +- .../impl/presentation/allpools/AllPoolsScreen.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index c77a4bbe51..54e4e1fc89 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -638,7 +638,7 @@ Your %s pooled Your pools Your supply to Liquidity pools has been successfully completed - All pools + Available pools Reset to default Swap Swapped diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 08de2a7fcb..5b223d8c4d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -105,7 +105,7 @@ fun AllPoolsScreen( modifier = Modifier.wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_all_pools), + title = stringResource(id = R.string.pl_available_pools), onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) state.allPools.forEach { pool -> @@ -134,7 +134,7 @@ fun ShimmerPoolList(size: Int = 10) { modifier = Modifier.wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_all_pools), + title = stringResource(id = R.string.pl_available_pools), onMoreClick = null ) repeat(size) { From 15147bc9e1750111373c502ff5fcd86a2ec22907 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 7 Aug 2024 12:34:16 +0500 Subject: [PATCH 41/84] FLW-4821 We should replace the button --- .../liquiditypools/impl/presentation/PoolsFlowFragment.kt | 7 +------ .../liquiditypools/impl/presentation/PoolsFlowViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index ff188d69ae..c8fece18ac 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -266,14 +266,9 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment Date: Wed, 7 Aug 2024 14:29:07 +0500 Subject: [PATCH 42/84] =?UTF-8?q?FLW-4824=20Cross=20button=20on=20the=20ba?= =?UTF-8?q?nner=20doesn=E2=80=99t=20work?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swap_tokens/SwapTokensContent.kt | 21 +++++++++++++++---- .../swap_tokens/SwapTokensViewModel.kt | 9 +++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index c73268ecb2..87e34f28e6 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -75,6 +75,7 @@ data class SwapTokensContentViewState( val swapDetailsViewState: SwapDetailsViewState?, val isLoading: Boolean, val networkFeeViewState: LoadingState, + val showLiquidityBanner: Boolean, val hasReadDisclaimer: Boolean, val isSoftKeyboardOpen: Boolean ) { @@ -88,6 +89,7 @@ data class SwapTokensContentViewState( swapDetailsViewState = null, isLoading = false, networkFeeViewState = LoadingState.Loaded(null), + showLiquidityBanner = true, hasReadDisclaimer = false, isSoftKeyboardOpen = false ) @@ -126,6 +128,8 @@ interface SwapTokensCallbacks { fun onDisclaimerClick() fun onPoolsClick() + + fun onLiquidityBannerClose() } @Composable @@ -263,7 +267,11 @@ fun SwapTokensContent( } if (state.fromAmountInputViewState.tokenName == null && state.toAmountInputViewState.tokenName == null) { - Banners(callbacks) + Banners( + showLiquidity = state.showLiquidityBanner, + showDemeter = false, + callback = callbacks + ) } AccentButton( @@ -405,18 +413,21 @@ private fun MarketLabel( @OptIn(ExperimentalFoundationApi::class) @Composable private fun Banners( + showLiquidity: Boolean, + showDemeter: Boolean, callback: SwapTokensCallbacks, autoPlay: Boolean = true ) { - val bannerLiquidityPools: @Composable (() -> Unit) = + val bannerLiquidityPools: @Composable (() -> Unit)? = if (showLiquidity) { { BannerLiquidityPools( onShowMoreClick = callback::onPoolsClick, - onCloseClick = {} + onCloseClick = callback::onLiquidityBannerClose ) } + } else null - val bannerDemeter: @Composable (() -> Unit)? = if (false) { + val bannerDemeter: @Composable (() -> Unit)? = if (showDemeter) { { BannerDemeter( onShowMoreClick = {}, @@ -485,6 +496,7 @@ fun SwapTokensContentPreview() { swapDetailsViewState = null, isLoading = false, networkFeeViewState = LoadingState.Loading(), + showLiquidityBanner = true, hasReadDisclaimer = false, isSoftKeyboardOpen = false ) @@ -504,6 +516,7 @@ fun SwapTokensContentPreview() { override fun onQuickAmountInput(value: Double) {} override fun onDisclaimerClick() {} override fun onPoolsClick() {} + override fun onLiquidityBannerClose() {} } SwapTokensContent( diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt index 1594bfb078..be2a9f295c 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt @@ -134,6 +134,7 @@ class SwapTokensViewModel @Inject constructor( ) private val isLoading = MutableStateFlow(false) + private val isShowBannerLiquidity = MutableStateFlow(true) private var initialFee = BigDecimal.ZERO private val availableDexPathsFlow: MutableStateFlow?> = MutableStateFlow(null) @@ -280,9 +281,10 @@ class SwapTokensViewModel @Inject constructor( swapDetailsViewState, networkFeeViewStateFlow, isLoading, + isShowBannerLiquidity, polkaswapInteractor.observeHasReadDisclaimer(), isSoftKeyboardOpenFlow - ) { fromAmountInput, toAmountInput, selectedMarket, swapDetails, networkFeeState, isLoading, hasReadDisclaimer, isSoftKeyboardOpen -> + ) { fromAmountInput, toAmountInput, selectedMarket, swapDetails, networkFeeState, isLoading, isShowBannerLiquidity, hasReadDisclaimer, isSoftKeyboardOpen -> SwapTokensContentViewState( fromAmountInputViewState = fromAmountInput, toAmountInputViewState = toAmountInput, @@ -290,6 +292,7 @@ class SwapTokensViewModel @Inject constructor( swapDetailsViewState = swapDetails, networkFeeViewState = networkFeeState, isLoading = isLoading, + showLiquidityBanner = isShowBannerLiquidity, hasReadDisclaimer = hasReadDisclaimer, isSoftKeyboardOpen = isSoftKeyboardOpen ) @@ -730,4 +733,8 @@ class SwapTokensViewModel @Inject constructor( override fun onPoolsClick() { polkaswapRouter.openPools() } + + override fun onLiquidityBannerClose() { + isShowBannerLiquidity.value = false + } } From 4e57e95f96a7d8baee23bb897549db5abcd65ab9 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 7 Aug 2024 14:34:22 +0500 Subject: [PATCH 43/84] FLW-4826 There should be MORE button if we have more than 5 user pools --- .../impl/presentation/allpools/AllPoolsPresenter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 210d43b94a..b11a05c185 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -13,6 +13,7 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlin.math.max import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -101,7 +102,7 @@ class AllPoolsPresenter @Inject constructor( val userPools = poolLists[true].orEmpty() val otherPools = poolLists[false].orEmpty() - val shownUserPools = userPools.take(10) + val shownUserPools = userPools.take(5) val shownOtherPools = otherPools.take(10) val hasExtraUserPools = shownUserPools.size < userPools.size From ffc5592294d899412d50f7ce8d66fef0d8a48f0a Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 7 Aug 2024 14:50:45 +0500 Subject: [PATCH 44/84] FLW-4828 On the Confirm screen there are no amounts --- .../LiquidityAddConfirmPresenter.kt | 2 -- .../LiquidityAddConfirmScreen.kt | 22 ++++--------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index fe9c745889..380401acac 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -107,9 +107,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( assetBase = assetBase.configuration, assetTarget = assetTarget.configuration, baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), - baseFiat = screenArgs.amountBase.applyFiatRate(assetBase.fiatRate)?.formatFiat(assetBase.fiatSymbol).orEmpty(), targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), - targetFiat = screenArgs.amountTarget.applyFiatRate(assetTarget.fiatRate)?.formatFiat(assetTarget.fiatSymbol).orEmpty(), apy = screenArgs.apy ) }.launchIn(coroutineScope) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index f077910e6f..649283c6fc 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -51,9 +51,7 @@ data class LiquidityAddConfirmState( val assetBase: Asset? = null, val assetTarget: Asset? = null, val baseAmount: String = "", - val baseFiat: String = "", val targetAmount: String = "", - val targetFiat: String = "", val slippage: String = "0.5%", val apy: String? = null, val feeInfo: FeeInfoViewState = FeeInfoViewState.default, @@ -114,7 +112,7 @@ fun LiquidityAddConfirmScreen( horizontalArrangement = Arrangement.Center ) { Text( - text = state.assetBase?.symbol?.uppercase().orEmpty(), + text = state.baseAmount, style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -132,7 +130,7 @@ fun LiquidityAddConfirmScreen( MarginHorizontal(margin = 8.dp) Text( - text = state.assetTarget?.symbol?.uppercase().orEmpty(), + text = state.targetAmount, style = MaterialTheme.customTypography.header3, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -182,20 +180,6 @@ fun LiquidityAddConfirmScreen( ), onClick = { callbacks.onAddItemClick(ITEM_FEE_ID)} ) - InfoTableItem( - TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetBase?.symbol?.uppercase().orEmpty()), - value = state.baseAmount, - additionalValue = state.baseFiat - ) - ) - InfoTableItem( - TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetTarget?.symbol?.uppercase().orEmpty()), - value = state.targetAmount, - additionalValue = state.targetFiat - ) - ) MarginVertical(margin = 8.dp) } } @@ -223,6 +207,8 @@ fun LiquidityAddConfirmScreen( private fun PreviewLiquidityAddConfirmScreen() { LiquidityAddConfirmScreen( state = LiquidityAddConfirmState( + baseAmount = "100.003 XOR", + targetAmount = "12340443,24312 PSWAP", slippage = "0.5%", apy = "23.3%", feeInfo = FeeInfoViewState.default, From 5e4aec4d21b12c54d9f6ca4a0991f9a0f7f69c36 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 7 Aug 2024 16:39:52 +0500 Subject: [PATCH 45/84] FLW-4827 Bugs on main screen of Pools --- common/src/main/res/values/strings.xml | 2 +- .../impl/presentation/PoolsFlowViewModel.kt | 41 +++++++++++-------- .../allpools/AllPoolsPresenter.kt | 2 +- .../presentation/allpools/AllPoolsScreen.kt | 32 +++++++-------- .../allpools/BasicPoolListItem.kt | 6 ++- .../poollist/PoolListPresenter.kt | 2 +- 6 files changed, 48 insertions(+), 37 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 54e4e1fc89..f9dbecd457 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -636,7 +636,7 @@ + %d others Your %s pooled - Your pools + User pools Your supply to Liquidity pools has been successfully completed Available pools Reset to default diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index ee9cecc78c..567a76aa26 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -8,9 +8,12 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.models.TextModel +import jp.co.soramitsu.common.compose.theme.greenText +import jp.co.soramitsu.common.compose.theme.white50 import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat +import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsPresenter import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreenInterface @@ -39,6 +42,7 @@ import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.NavAction import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted @@ -49,7 +53,6 @@ import kotlinx.coroutines.launch @HiltViewModel class PoolsFlowViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, allPoolsPresenter: AllPoolsPresenter, poolListPresenter: PoolListPresenter, poolDetailsPresenter: PoolDetailsPresenter, @@ -116,7 +119,6 @@ class PoolsFlowViewModel @Inject constructor( internalPoolsRouter.openAllPoolsScreen() launch { -// poolsInteractor.updateApy() poolsInteractor.updatePools(poolsInteractor.poolsChainId) } } @@ -136,14 +138,14 @@ class PoolsFlowViewModel @Inject constructor( LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName -> { val destinationArgs = internalPoolsRouter.destination(LiquidityPoolsNavGraphRoute.ListPoolsScreen::class.java) - val title = if (destinationArgs?.isUserPools == true) { - "Your pools" + val titleId = if (destinationArgs?.isUserPools == true) { + R.string.pl_user_pools } else { - "Available pools" + R.string.pl_available_pools } LoadingState.Loaded( - TextModel.SimpleString(title) + TextModel.ResId(titleId) ) } @@ -191,20 +193,27 @@ class PoolsFlowViewModel @Inject constructor( } } -fun BasicPoolData.toListItemState(): BasicPoolListItemState? { - val tvl = this.baseToken.token.fiatRate?.times(BigDecimal(2)) - ?.multiply(this.baseReserves) +fun CommonPoolData.toListItemState(): BasicPoolListItemState? { + val tvl = basic.baseToken.token.fiatRate?.times(BigDecimal(2)) + ?.multiply(basic.baseReserves) + ?.formatFiat(basic.baseToken.token.fiatSymbol).orEmpty() - val baseTokenId = this.baseToken.token.configuration.currencyId ?: return null - val targetTokenId = this.targetToken?.token?.configuration?.currencyId ?: return null + val baseTokenId = basic.baseToken.token.configuration.currencyId ?: return null + val targetTokenId = basic.targetToken?.token?.configuration?.currencyId ?: return null + + val baseSymbol = basic.baseToken.token.configuration.symbol + val targetSymbol = basic.targetToken?.token?.configuration?.symbol + val userPooledInfo = user?.let { "${it.basePooled.formatCrypto(baseSymbol)} - ${it.targetPooled.formatCrypto(targetSymbol)}" } + val text2Color = if (user == null) white50 else user.let { greenText } return BasicPoolListItemState( ids = StringPair(baseTokenId, targetTokenId), - token1Icon = this.baseToken.token.configuration.iconUrl, - token2Icon = this.targetToken?.token?.configuration?.iconUrl.orEmpty(), - text1 = "${this.baseToken.token.configuration.symbol}-${this.targetToken?.token?.configuration?.symbol}".uppercase(), - text2 = tvl?.formatFiat(this.baseToken.token.fiatSymbol).orEmpty(), - text3 = this.sbapy?.let { + token1Icon = basic.baseToken.token.configuration.iconUrl, + token2Icon = basic.targetToken?.token?.configuration?.iconUrl.orEmpty(), + text1 = "$baseSymbol-$targetSymbol".uppercase(), + text2 = userPooledInfo ?: tvl, + text2Color = text2Color, + text3 = basic.sbapy?.let { "%s%%".format(it.toBigDecimal().formatCrypto()) }.orEmpty(), text4 = "Earn PSWAP" diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index b11a05c185..021dfdfcfa 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -81,7 +81,7 @@ class AllPoolsPresenter @Inject constructor( println("!!! allPools grouped users = ${it[true]?.size}; other = ${it[false]?.size}") // it.map(BasicPoolData::toListItemState) it.mapValues { it -> - it.value.mapNotNull { it.basic.toListItemState() } + it.value.mapNotNull { it.toListItemState() } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 5b223d8c4d..dcd6e21b3c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -68,7 +68,12 @@ fun AllPoolsScreen( MarginVertical(margin = 8.dp) if (state.isLoading || (state.userPools.isEmpty() && state.allPools.isEmpty())) { - ShimmerPoolList() + BackgroundCorneredWithBorder( + modifier = Modifier + .padding(horizontal = 16.dp) + ) { + ShimmerPoolList() + } } else { if (state.userPools.isNotEmpty()) { BackgroundCorneredWithBorder( @@ -81,7 +86,7 @@ fun AllPoolsScreen( .wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_your_pools), + title = stringResource(id = R.string.pl_user_pools), onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } ) state.userPools.forEach { pool -> @@ -125,21 +130,16 @@ fun AllPoolsScreen( @Composable fun ShimmerPoolList(size: Int = 10) { - BackgroundCorneredWithBorder( - modifier = Modifier - .padding(horizontal = 16.dp) + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = Modifier.wrapContentHeight() ) { - Column( - verticalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.wrapContentHeight() - ) { - PoolGroupHeader( - title = stringResource(id = R.string.pl_available_pools), - onMoreClick = null - ) - repeat(size) { - BasicPoolShimmerItem(modifier = Modifier.padding(vertical = Dimens.x1)) - } + PoolGroupHeader( + title = stringResource(id = R.string.pl_available_pools), + onMoreClick = null + ) + repeat(size) { + BasicPoolShimmerItem(modifier = Modifier.padding(vertical = Dimens.x1)) } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index dd5f698bb2..9d5278c801 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -49,6 +50,7 @@ data class BasicPoolListItemState( val token2Icon: String, val text1: String, val text2: String, + val text2Color: Color = white50, val text3: String, val text4: String? = null, ) @@ -144,8 +146,8 @@ fun BasicPoolListItem( ) Text( - modifier = Modifier.wrapContentHeight(), - color = white50, + modifier = Modifier.wrapContentHeight().padding(start = 6.dp), + color = state.text2Color, style = MaterialTheme.customTypography.body2, text = state.text2, maxLines = 1, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index bfb11626da..1773e6285f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -59,7 +59,7 @@ class PoolListPresenter @Inject constructor( compareNullDesc(o1.basic.tvl, o2.basic.tvl) } }.map { - it.mapNotNull{ it.basic.toListItemState() } + it.mapNotNull{ it.toListItemState() } } } From e8cc9a3bebe8c78bca2afa71d69e8ff57baaa56f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Thu, 8 Aug 2024 19:18:14 +0500 Subject: [PATCH 46/84] FLW-4822 The banner disappears when we are selecting the asset --- .../presentation/swap_tokens/SwapTokensContent.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index 87e34f28e6..5b50b5d25b 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -266,13 +266,11 @@ fun SwapTokensContent( MarginVertical(margin = 16.dp) } - if (state.fromAmountInputViewState.tokenName == null && state.toAmountInputViewState.tokenName == null) { - Banners( - showLiquidity = state.showLiquidityBanner, - showDemeter = false, - callback = callbacks - ) - } + Banners( + showLiquidity = state.showLiquidityBanner, + showDemeter = false, + callback = callbacks + ) AccentButton( modifier = Modifier From 857f8d97b2303c7cdf9dd6ddfcfb89cc494c5255 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 9 Aug 2024 12:01:16 +0500 Subject: [PATCH 47/84] FLW-4822 The banner disappears when we are selecting the asset --- .../swap_tokens/SwapTokensContent.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt index 5b50b5d25b..22be114118 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensContent.kt @@ -249,7 +249,15 @@ fun SwapTokensContent( callbacks = callbacks ) } - } + if (state.showLiquidityBanner && state.isSoftKeyboardOpen.not()) { + Spacer(modifier = Modifier.weight(1f)) + + Banners( + showLiquidity = state.showLiquidityBanner, + showDemeter = false, + callback = callbacks + ) + } } if (state.hasReadDisclaimer.not()) { Box(modifier = modifier.padding(horizontal = 16.dp)) { Notification( @@ -266,12 +274,6 @@ fun SwapTokensContent( MarginVertical(margin = 16.dp) } - Banners( - showLiquidity = state.showLiquidityBanner, - showDemeter = false, - callback = callbacks - ) - AccentButton( modifier = Modifier .height(48.dp) From 04b60e1907dbafb7d1be72f481c9c78353f62e70 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 9 Aug 2024 12:08:29 +0500 Subject: [PATCH 48/84] add shimmer to amount --- .../common/compose/component/AmountInput.kt | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt index 08d3ac0feb..f1dd324c74 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import com.valentinilk.shimmer.shimmer import java.math.BigDecimal import jp.co.soramitsu.common.R import jp.co.soramitsu.common.compose.theme.FearlessTheme @@ -51,8 +52,10 @@ data class AmountInputViewState( val isFocused: Boolean = false, val allowAssetChoose: Boolean = false, val precision: Int = MAX_DECIMALS_8, - val inputEnabled: Boolean = true + val inputEnabled: Boolean = true, + val isShimmerAmounts: Boolean = false ) { + companion object { val defaultObj = AmountInputViewState( tokenName = null, @@ -120,7 +123,16 @@ fun AmountInput( val title = state.title ?: stringResource(id = R.string.common_amount) H5(text = title, modifier = Modifier.weight(1f), color = black2) state.fiatAmount?.let { - B1(text = it, modifier = Modifier.weight(1f), textAlign = TextAlign.End, color = black2) + B1( + text = it, + modifier = Modifier + .weight(1f) + .then( + if (state.isShimmerAmounts) Modifier.shimmer() else Modifier + ), + textAlign = TextAlign.End, + color = black2 + ) } } @@ -155,7 +167,14 @@ fun AmountInput( modifier = Modifier .fillMaxWidth() .testTag("InputAmountField" + (state.tokenName.orEmpty())) - .wrapContentHeight(), + .wrapContentHeight() + .then( + if (state.isShimmerAmounts) { + Modifier.shimmer() + } else { + Modifier + } + ), onFocusChanged = onInputFocusChange, textStyle = MaterialTheme.customTypography.displayS.copy(textAlign = TextAlign.End, color = textColorState), enabled = state.inputEnabled, From 8c55d2333009bb1f5de47360745b799491fc283d Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 9 Aug 2024 12:10:49 +0500 Subject: [PATCH 49/84] refactor --- .../impl/domain/PoolsInteractorImpl.kt | 2 + .../liquidityadd/LiquidityAddPresenter.kt | 299 +++++++++--------- .../usecase/ValidateAddLiquidityUseCase.kt | 16 +- 3 files changed, 156 insertions(+), 161 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index dd01d4e581..ed9fe72254 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -16,6 +16,7 @@ import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest @@ -50,6 +51,7 @@ class PoolsInteractorImpl( }.mapNotNull { it } .distinctUntilChanged() + @OptIn(ExperimentalCoroutinesApi::class) override fun subscribePoolsCacheCurrentAccount(): Flow> { return soraPoolsAddressFlow.flatMapLatest { address -> poolsRepository.subscribePools(address) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 6a631f6494..3abb7fe233 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -7,10 +7,13 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.isZero import jp.co.soramitsu.common.base.errors.ValidationException +import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.common.utils.Event import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 import jp.co.soramitsu.common.utils.applyFiatRate +import jp.co.soramitsu.common.utils.combine import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail @@ -53,6 +56,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class LiquidityAddPresenter @Inject constructor( @@ -68,174 +72,185 @@ class LiquidityAddPresenter @Inject constructor( private val enteredBaseAmountFlow = MutableStateFlow(BigDecimal.ZERO) private val enteredTargetAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val uiBaseAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private val uiTargetAmountFlow = MutableStateFlow(BigDecimal.ZERO) + private var amountBase: BigDecimal = BigDecimal.ZERO private var amountTarget: BigDecimal = BigDecimal.ZERO private val isBaseAmountFocused = MutableStateFlow(false) private val isTargetAmountFocused = MutableStateFlow(false) + private val isButtonLoading = MutableStateFlow(false) + private val isCalculatingAmounts = MutableStateFlow(null) + private var desired: WithDesired = WithDesired.INPUT private val _stateSlippage = MutableStateFlow(0.5) - val stateSlippage = _stateSlippage.asStateFlow() + private val stateSlippage = _stateSlippage.asStateFlow() + + private val resetFlow = MutableStateFlow(Event(Unit)) private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() + .distinctUntilChanged(areArgsEquivalent()) .onEach { - resetState() + resetFlow.emit(Event(Unit)) } .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - private fun resetState() { - amountTarget = BigDecimal.ZERO - amountBase = BigDecimal.ZERO - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - tokenAmount = BigDecimal.ZERO, - fiatAmount = null - ), - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - tokenAmount = BigDecimal.ZERO, - fiatAmount = null - ) - ) + private fun areArgsEquivalent(): ( + old: LiquidityPoolsNavGraphRoute.LiquidityAddScreen, + new: LiquidityPoolsNavGraphRoute.LiquidityAddScreen + ) -> Boolean = { old, new -> + old.routeName == new.routeName && + old.ids.first == new.ids.first && + old.ids.second == new.ids.second } @OptIn(ExperimentalCoroutinesApi::class) - val assetsInPoolFlow = screenArgsFlow.distinctUntilChanged().flatMapLatest { screenArgs -> + val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids val chainId = poolsInteractor.poolsChainId println("!!! assetsInPoolFlow ADD ids = $ids") - val assetsFlow = walletInteractor.assetsFlow().mapNotNull { - val firstInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.first - && it.asset.token.configuration.chainId == chainId - } - val secondInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.second - && it.asset.token.configuration.chainId == chainId - } + val chainAssets = chainsRepository.getChain(chainId).assets + val baseAsset = chainAssets.firstOrNull { it.currencyId == ids.first }?.let { + walletInteractor.getCurrentAssetOrNull(chainId, it.id) + } + val targetAsset = chainAssets.firstOrNull { it.currencyId == ids.second }?.let { + walletInteractor.getCurrentAssetOrNull(chainId, it.id) + } - println("!!! assetsInPoolFlow ADD result: $firstInPair; $secondInPair") - if (firstInPair == null || secondInPair == null) { - return@mapNotNull null + val assetsFlow = flowOf { + if (baseAsset == null || targetAsset == null) { + null } else { - firstInPair to secondInPair + baseAsset to targetAsset } - } + }.mapNotNull { it } + +// val assetsFlow2 = walletInteractor.assetsFlow().mapNotNull { +// val firstInPair = it.firstOrNull { +// it.asset.token.configuration.currencyId == ids.first +// && it.asset.token.configuration.chainId == chainId +// } +// val secondInPair = it.firstOrNull { +// it.asset.token.configuration.currencyId == ids.second +// && it.asset.token.configuration.chainId == chainId +// } +// +// println("!!! assetsInPoolFlow ADD result: $firstInPair; $secondInPair") +// if (firstInPair == null || secondInPair == null) { +// return@mapNotNull null +// } else { +// firstInPair.asset to secondInPair.asset +// } +// } assetsFlow }.distinctUntilChanged() private val tokensInPoolFlow = assetsInPoolFlow.map { - it.first.asset.token.configuration to it.second.asset.token.configuration + it.first.token.configuration to it.second.token.configuration }.distinctUntilChanged() - @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) + @OptIn(FlowPreview::class) private fun subscribeState(coroutineScope: CoroutineScope) { println("!!! AddPresenter subscribeState") - screenArgsFlow.flatMapLatest { screenargs -> - println("!!! AddPresenter screenArgsFlow = $screenargs") - poolsInteractor.getPoolData( - baseTokenId = screenargs.ids.first, - targetTokenId = screenargs.ids.second - ).onEach { - stateFlow.value = stateFlow.value.copy( - apy = it.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" } - ) + enteredBaseAmountFlow + .onEach { + desired = WithDesired.INPUT + isCalculatingAmounts.value = desired } - }.launchIn(coroutineScope) - - assetsInPoolFlow.onEach { (assetBase, assetTarget) -> - val totalBaseCrypto = assetBase.asset.total?.formatCrypto(assetBase.asset.token.configuration.symbol).orEmpty() - val totalBaseFiat = assetBase.asset.fiatAmount?.formatFiat(assetBase.asset.token.fiatSymbol) - val argsBase = totalBaseCrypto + totalBaseFiat?.let { " ($it)" }.orEmpty() - val totalBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) - - val totalTargetCrypto = assetTarget.asset.total?.formatCrypto(assetTarget.asset.token.configuration.symbol).orEmpty() - val totalTargetFiat = assetTarget.asset.fiatAmount?.formatFiat(assetTarget.asset.token.fiatSymbol) - val argsTarget = totalTargetCrypto + totalTargetFiat?.let { " ($it)" }.orEmpty() - val totalTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) - - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - tokenName = assetBase.asset.token.configuration.symbol, - tokenImage = assetBase.asset.token.configuration.iconUrl, - totalBalance = totalBaseBalance, - ), - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - tokenName = assetTarget.asset.token.configuration.symbol, - tokenImage = assetTarget.asset.token.configuration.iconUrl, - totalBalance = totalTargetBalance, - ) - ) - }.launchIn(coroutineScope) - - enteredBaseAmountFlow.onEach { - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.first?.asset?.token?.fiatSymbol), - tokenAmount = it, - ) - ) - } .debounce(900) .onEach { amount -> amountBase = amount - desired = WithDesired.INPUT updateAmounts() }.launchIn(coroutineScope) - enteredTargetAmountFlow.onEach { - stateFlow.value = stateFlow.value.copy( - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - fiatAmount = it.applyFiatRate(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatRate)?.formatFiat(assetsInPoolFlow.firstOrNull()?.second?.asset?.token?.fiatSymbol), - tokenAmount = it - ), - ) - } + enteredTargetAmountFlow + .onEach { + desired = WithDesired.OUTPUT + isCalculatingAmounts.value = desired + } .debounce(900) .onEach { amount -> amountTarget = amount - desired = WithDesired.OUTPUT updateAmounts() }.launchIn(coroutineScope) - - isBaseAmountFocused.onEach { - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - isFocused = it - ), - ) - }.launchIn(coroutineScope) - - isTargetAmountFocused.onEach { - stateFlow.value = stateFlow.value.copy( - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - isFocused = it - ), - ) - }.launchIn(coroutineScope) - - stateSlippage.onEach { - stateFlow.value = stateFlow.value.copy( - slippage = "$it%" - ) - }.launchIn(coroutineScope) - - feeInfoViewStateFlow.onEach { - stateFlow.value = stateFlow.value.copy( - feeInfo = it - ) - updateButtonState() - }.launchIn(coroutineScope) } + @OptIn(ExperimentalCoroutinesApi::class) + val poolFlow = screenArgsFlow.flatMapLatest { screenargs -> + poolsInteractor.getPoolData( + baseTokenId = screenargs.ids.first, + targetTokenId = screenargs.ids.second + ) + } - private val stateFlow = MutableStateFlow(LiquidityAddState()) - + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { subscribeState(coroutineScope) - return stateFlow + + return resetFlow.onEach { + amountTarget = BigDecimal.ZERO + amountBase = BigDecimal.ZERO + uiBaseAmountFlow.value = BigDecimal.ZERO + uiTargetAmountFlow.value = BigDecimal.ZERO + }.debounce(200).flatMapLatest { + combine( + poolFlow, + assetsInPoolFlow, + uiBaseAmountFlow, + uiTargetAmountFlow, + isBaseAmountFocused, + isTargetAmountFocused, + stateSlippage, + feeInfoViewStateFlow, + isButtonLoading, + isCalculatingAmounts + ) { pool, (assetBase, assetTarget), baseShown, targetShown, baseFocused, targetFocused, slippage, feeInfo, isButtonLoading, isCalulatingAmount -> + val totalBaseCrypto = assetBase.total?.formatCrypto(assetBase.token.configuration.symbol).orEmpty() + val totalBaseFiat = assetBase.fiatAmount?.formatFiat(assetBase.token.fiatSymbol) + val argsBase = totalBaseCrypto + totalBaseFiat?.let { " ($it)" }.orEmpty() + val totalBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) + + val totalTargetCrypto = assetTarget.total?.formatCrypto(assetTarget.token.configuration.symbol).orEmpty() + val totalTargetFiat = assetTarget.fiatAmount?.formatFiat(assetTarget.token.fiatSymbol) + val argsTarget = totalTargetCrypto + totalTargetFiat?.let { " ($it)" }.orEmpty() + val totalTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) + + val isButtonEnabled = amountBase.moreThanZero() && + amountTarget.moreThanZero() && + feeInfo.feeAmount != null && + isCalculatingAmounts.value == null + + LiquidityAddState( + apy = pool.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty(), + slippage = "$slippage%", + feeInfo = feeInfo, + buttonEnabled = isButtonEnabled, + buttonLoading = isButtonLoading, + baseAmountInputViewState = AmountInputViewState( + tokenName = assetBase.token.configuration.symbol, + tokenImage = assetBase.token.configuration.iconUrl, + totalBalance = totalBaseBalance, + tokenAmount = baseShown, + fiatAmount = baseShown.applyFiatRate(assetBase.token.fiatRate)?.formatFiat(assetBase.token.fiatSymbol), + isFocused = baseFocused, + isShimmerAmounts = isCalulatingAmount == WithDesired.OUTPUT + ), + targetAmountInputViewState = AmountInputViewState( + tokenName = assetTarget.token.configuration.symbol, + tokenImage = assetTarget.token.configuration.iconUrl, + totalBalance = totalTargetBalance, + tokenAmount = targetShown, + fiatAmount = targetShown.applyFiatRate(assetTarget.token.fiatRate)?.formatFiat(assetTarget.token.fiatSymbol), + isFocused = targetFocused, + isShimmerAmounts = isCalulatingAmount == WithDesired.INPUT + ) + ) + } + }.stateIn(coroutineScope, SharingStarted.Lazily, LiquidityAddState()) } // suspend fun getPoolDataDto(): PoolDataDto? { @@ -261,37 +276,17 @@ class LiquidityAddPresenter @Inject constructor( } if (desired == WithDesired.INPUT) { - val tokenTarget = assetsInPoolFlow.firstOrNull()?.second?.asset?.token - stateFlow.value = stateFlow.value.copy( - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - tokenAmount = scaledTargetAmount, - fiatAmount = scaledTargetAmount.applyFiatRate(tokenTarget?.fiatRate)?.formatFiat(tokenTarget?.fiatSymbol), - - ) - ) + uiTargetAmountFlow.value = scaledTargetAmount amountTarget = scaledTargetAmount } else { - val tokenBase = assetsInPoolFlow.firstOrNull()?.first?.asset?.token - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - tokenAmount = scaledTargetAmount, - fiatAmount = scaledTargetAmount.applyFiatRate(tokenBase?.fiatRate)?.formatFiat(tokenBase?.fiatSymbol), - ) - ) + uiBaseAmountFlow.value = scaledTargetAmount amountBase = scaledTargetAmount } } - updateButtonState() - } - - private fun updateButtonState() { - val isButtonEnabled = amountTarget.moreThanZero() && amountTarget.moreThanZero() && stateFlow.value.feeInfo.feeAmount != null - println("!!! updateButtonState: $amountTarget; $amountTarget; ${stateFlow.value.feeInfo.feeAmount}") - println("!!! updateButtonState: isButtonEnabled = $isButtonEnabled") - stateFlow.value = stateFlow.value.copy( - buttonEnabled = isButtonEnabled - ) + if (isCalculatingAmounts.value == desired) { + isCalculatingAmounts.value = null + } } @OptIn(ExperimentalCoroutinesApi::class) @@ -319,8 +314,8 @@ class LiquidityAddPresenter @Inject constructor( baseAmount = baseAmount, reservesFirst = reservesFirst, reservesSecond = reservesSecond, - precisionFirst = baseAsset.asset.token.configuration.precision, - precisionSecond = targetAsset.asset.token.configuration.precision, + precisionFirst = baseAsset.token.configuration.precision, + precisionSecond = targetAsset.token.configuration.precision, desired = desired ) } @@ -405,9 +400,7 @@ class LiquidityAddPresenter @Inject constructor( } private fun setButtonLoading(loading: Boolean) { - stateFlow.value = stateFlow.value.copy( - buttonLoading = loading - ) + isButtonLoading.value = loading } override fun onAddReviewClick() { @@ -444,7 +437,9 @@ class LiquidityAddPresenter @Inject constructor( } val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, stateFlow.value.apy.orEmpty()) + val apy = poolFlow.firstOrNull()?.basic?.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty() + + internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, apy) }.invokeOnCompletion { println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { @@ -456,16 +451,14 @@ class LiquidityAddPresenter @Inject constructor( override fun onAddBaseAmountChange(amount: BigDecimal) { enteredBaseAmountFlow.value = amount + uiBaseAmountFlow.value = amount amountBase = amount - - updateButtonState() } override fun onAddTargetAmountChange(amount: BigDecimal) { enteredTargetAmountFlow.value = amount + uiTargetAmountFlow.value = amount amountTarget = amount - - updateButtonState() } override fun onAddBaseAmountFocusChange(isFocused: Boolean) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index 1c28037502..72f94e82ff 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -4,13 +4,13 @@ import java.math.BigDecimal import javax.inject.Inject import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.wallet.api.domain.TransferValidationResult -import jp.co.soramitsu.wallet.impl.domain.model.AssetWithStatus +import jp.co.soramitsu.wallet.impl.domain.model.Asset class ValidateAddLiquidityUseCase @Inject constructor() { operator fun invoke( - assetBase: AssetWithStatus, - assetTarget: AssetWithStatus, + assetBase: Asset, + assetTarget: Asset, utilityAssetId: String, utilityAmount: BigDecimal, amountBase: BigDecimal, @@ -19,14 +19,14 @@ class ValidateAddLiquidityUseCase @Inject constructor() { ): Result { return runCatching { val isEnoughAmountBase = amountBase + feeAmount.takeIf { - assetBase.asset.token.configuration.id == utilityAssetId - }.orZero() < assetBase.asset.total.orZero() + assetBase.token.configuration.id == utilityAssetId + }.orZero() < assetBase.total.orZero() val isEnoughAmountTarget = amountTarget + feeAmount.takeIf { - assetTarget.asset.token.configuration.id == utilityAssetId - }.orZero() < assetTarget.asset.total.orZero() + assetTarget.token.configuration.id == utilityAssetId + }.orZero() < assetTarget.total.orZero() - val isEnoughAmountFee = if (utilityAssetId in listOf(assetBase.asset.token.configuration.id, assetTarget.asset.token.configuration.id)) { + val isEnoughAmountFee = if (utilityAssetId in listOf(assetBase.token.configuration.id, assetTarget.token.configuration.id)) { true } else { feeAmount < utilityAmount From 6fbdca3b15490a51883c9adfc68f50ed96af909c Mon Sep 17 00:00:00 2001 From: Deneath Date: Tue, 13 Aug 2024 13:43:10 +0700 Subject: [PATCH 50/84] refactor data logic wip Signed-off-by: Deneath --- .../domain/interfaces/PoolsInteractor.kt | 4 +- .../impl/data/PoolsRepositoryImpl.kt | 449 +++++++++--------- .../liquiditypools/impl/di/PoolsModule.kt | 12 +- .../impl/domain/PoolsInteractorImpl.kt | 23 +- .../impl/presentation/PoolsFlowViewModel.kt | 29 +- .../allpools/AllPoolsPresenter.kt | 132 +++-- .../LiquidityRemovePresenter.kt | 102 ++-- .../pooldetails/PoolDetailsPresenter.kt | 27 +- .../poollist/PoolListPresenter.kt | 39 +- .../api/domain/models/BasicPoolData.kt | 7 +- .../api/domain/models/UserPoolData.kt | 8 +- .../domain/interfaces/WalletInteractor.kt | 8 +- .../wallet/impl/di/WalletFeatureModule.kt | 10 +- .../impl/domain/WalletInteractorImpl.kt | 14 +- 14 files changed, 422 insertions(+), 442 deletions(-) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 6da6852716..8285cef3f0 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -1,12 +1,12 @@ package jp.co.soramitsu.liquiditypools.domain.interfaces -import java.math.BigDecimal import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow +import java.math.BigDecimal interface PoolsInteractor { val poolsChainId: String @@ -66,7 +66,7 @@ interface PoolsInteractor { slippageTolerance: Double ): String - suspend fun updatePools(chainId: ChainId) + suspend fun syncPools(chainId: ChainId) suspend fun observeRemoveLiquidity( chainId: ChainId, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index f4040bc99d..b9eb1ac376 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -1,9 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.data import androidx.room.withTransaction -import java.math.BigDecimal -import java.math.BigInteger -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.androidfoundation.format.addHexPrefix @@ -11,12 +8,8 @@ import jp.co.soramitsu.androidfoundation.format.mapBalance import jp.co.soramitsu.androidfoundation.format.safeCast import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.common.utils.fromHex -import jp.co.soramitsu.common.utils.mapList -import jp.co.soramitsu.common.utils.poolTBC -import jp.co.soramitsu.common.utils.poolXYK import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.coredb.AppDatabase import jp.co.soramitsu.coredb.dao.PoolDao @@ -29,11 +22,6 @@ import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData -import jp.co.soramitsu.polkaswap.api.models.Market -import jp.co.soramitsu.polkaswap.api.models.WithDesired -import jp.co.soramitsu.polkaswap.api.models.backStrings -import jp.co.soramitsu.polkaswap.api.models.toFilters -import jp.co.soramitsu.polkaswap.api.models.toMarkets import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool @@ -43,11 +31,9 @@ import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.removeLiquidity import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId +import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId import jp.co.soramitsu.runtime.network.subscriptionFlowCatching -import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.extensions.toHexString @@ -62,7 +48,6 @@ import jp.co.soramitsu.shared_utils.scale.dataType.uint32 import jp.co.soramitsu.shared_utils.scale.sizedByteArray import jp.co.soramitsu.shared_utils.scale.uint128 import jp.co.soramitsu.shared_utils.ss58.SS58Encoder.toAccountId -import jp.co.soramitsu.shared_utils.wsrpc.exception.RpcException import jp.co.soramitsu.shared_utils.wsrpc.executeAsync import jp.co.soramitsu.shared_utils.wsrpc.mappers.nonNull import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo @@ -75,18 +60,20 @@ import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChan import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount -import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.supervisorScope +import java.math.BigDecimal +import java.math.BigInteger +import javax.inject.Inject class PoolsRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, @@ -95,9 +82,9 @@ class PoolsRepositoryImpl @Inject constructor( private val walletRepository: WalletRepository, private val blockExplorerManager: BlockExplorerManager, private val poolDao: PoolDao, - private val db: AppDatabase, + private val db: AppDatabase ) : PoolsRepository { - override val poolsChainId = soraTestChainId + override val poolsChainId = soraMainChainId override suspend fun isPairAvailable( chainId: ChainId, @@ -114,7 +101,10 @@ class PoolsRepositoryImpl @Inject constructor( ) ) - return chainRegistry.awaitConnection(chainId).socketService.executeAsync(request, mapper = pojo().nonNull()) + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request, + mapper = pojo().nonNull() + ) } fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } @@ -209,28 +199,20 @@ class PoolsRepositoryImpl @Inject constructor( return tempApy } - override suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? { + override suspend fun getBasicPool( + chainId: ChainId, + baseTokenId: String, + targetTokenId: String + ): BasicPoolData? { val poolLocal = poolDao.getBasicPool(baseTokenId, targetTokenId) ?: return null val soraChain = chainRegistry.getChain(chainId) - val wallet = accountRepository.getSelectedMetaAccount() - val accountId = wallet.accountId(soraChain) - val soraAssets = soraChain.assets.mapNotNull { chainAsset -> - accountId?.let { - walletRepository.getAsset( - metaId = wallet.id, - accountId = accountId, - chainAsset = chainAsset, - minSupportedVersion = null - ) - } - } - + val soraAssets = soraChain.assets val baseAsset = soraAssets.firstOrNull { - it.token.configuration.currencyId == baseTokenId + it.currencyId == baseTokenId } ?: return null val targetAsset = soraAssets.firstOrNull { - it.token.configuration.currencyId == targetTokenId + it.currencyId == targetTokenId } return BasicPoolData( @@ -258,21 +240,12 @@ class PoolsRepositoryImpl @Inject constructor( val wallet = accountRepository.getSelectedMetaAccount() val accountId = wallet.accountId(soraChain) - val soraAssets = soraChain.assets.mapNotNull { chainAsset -> - accountId?.let { - walletRepository.getAsset( - metaId = wallet.id, - accountId = accountId, - chainAsset = chainAsset, - minSupportedVersion = null - ) - } - } + val soraAssets = soraChain.assets println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") soraAssets.forEach { asset -> - val currencyId = asset.token.configuration.currencyId + val currencyId = asset.currencyId val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } key?.let { getStateKeys(chainId, it).forEach { storageKey -> @@ -290,16 +263,18 @@ class PoolsRepositoryImpl @Inject constructor( val total = reserveAccount?.let { getPoolTotalIssuances(chainId, it) }?.let { - mapBalance(it, asset.token.configuration.precision) + mapBalance(it, asset.precision) } - val targetAsset = soraAssets.firstOrNull { it.token.configuration.currencyId == targetToken } - val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" + val targetAsset = + soraAssets.firstOrNull { it.currencyId == targetToken } + val reserveAccountAddress = + reserveAccount?.let { soraChain.addressOf(it) } ?: "" val element = BasicPoolData( baseToken = asset, targetToken = targetAsset, - baseReserves = mapBalance(reserves[0], asset.token.configuration.precision), - targetReserves = mapBalance(reserves[1], asset.token.configuration.precision), + baseReserves = mapBalance(reserves[0], asset.precision), + targetReserves = mapBalance(reserves[1], asset.precision), totalIssuance = total ?: BigDecimal.ZERO, reserveAccount = reserveAccountAddress, sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) @@ -335,14 +310,15 @@ class PoolsRepositoryImpl @Inject constructor( address.toAccountId() ) } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") - val poolProvidersFlow = chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(poolProvidersKey), - "state_unsubscribeStorage", - ).map { - it.map { it.storageChange().getSingleChange().orEmpty() } - }.map { - it.getOrNull().orEmpty() - } + val poolProvidersFlow = + chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( + SubscribeStorageRequest(poolProvidersKey), + "state_unsubscribeStorage", + ).map { + it.map { it.storageChange().getSingleChange().orEmpty() } + }.map { + it.getOrNull().orEmpty() + } emitAll(poolProvidersFlow) } @@ -352,24 +328,40 @@ class PoolsRepositoryImpl @Inject constructor( baseTokenId: String, targetTokenId: ByteArray ): PoolDataDto? { - val reserves = getPairWithXorReserves(chainId, baseTokenId, targetTokenId) - val totalIssuanceAndProperties = - getPoolTotalIssuanceAndProperties(chainId, baseTokenId, targetTokenId, address) - - if (reserves == null || totalIssuanceAndProperties == null) { - return null + return coroutineScope { + val reservesDeferred = + async { getPairWithXorReserves(chainId, baseTokenId, targetTokenId) } + val totalIssuanceAndPropertiesDeferred = + async { + getPoolTotalIssuanceAndProperties( + chainId, + baseTokenId, + targetTokenId, + address + ) + } + val chainDeferred = async { chainRegistry.getChain(chainId) } + + val reserves = kotlin.runCatching { reservesDeferred.await() }.getOrNull() + ?: return@coroutineScope null + + val totalIssuanceAndProperties = + kotlin.runCatching { totalIssuanceAndPropertiesDeferred.await() }.getOrNull() + ?: return@coroutineScope null + + val reservesAccount = chainDeferred.await() + .addressOf(totalIssuanceAndProperties.third) + + PoolDataDto( + baseTokenId, + targetTokenId.toHexString(true), + reserves.first, + reserves.second, + totalIssuanceAndProperties.first, + totalIssuanceAndProperties.second, + reservesAccount, + ) } - val reservesAccount = chainRegistry.getChain(chainId).addressOf(totalIssuanceAndProperties.third) - - return PoolDataDto( - baseTokenId, - targetTokenId.toHexString(true), - reserves.first, - reserves.second, - totalIssuanceAndProperties.first, - totalIssuanceAndProperties.second, - reservesAccount, - ) } override suspend fun calcAddLiquidityNetworkFee( @@ -436,25 +428,28 @@ class PoolsRepositoryImpl @Inject constructor( } private suspend fun getPoolBaseTokens(chainId: ChainId): List> { - val runtimeSnapshot = chainRegistry.getRuntimeOrNull(chainId) - val metadataStorage = runtimeSnapshot?.metadata - ?.module("DEXManager") - ?.storage("DEXInfos") - val partialKey = metadataStorage - ?.storageKey() ?: error("getPoolBaseTokenDexId storageKey not supported") + val runtimeSnapshot = chainRegistry.awaitRuntimeProvider(chainId).get() + val metadataStorage = runtimeSnapshot.metadata + .module("DEXManager") + .storage("DEXInfos") - return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + val partialKey = metadataStorage.storageKey() + val connection = chainRegistry.awaitConnection(chainId) + + val storageKeys = connection.socketService.executeAsync( request = StateKeys(listOf(partialKey)), mapper = pojoList().nonNull() - ).let { storageKeys -> - storageKeys.mapNotNull { storageKey -> - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = pojo().nonNull() - ).let { storage -> + ) + return supervisorScope { + val poolBaseTokensDeferred = storageKeys.map { storageKey -> + async { + val storage = connection.socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull() + ) val storageType = metadataStorage.type.value!! - val storageRawData = - storageType.fromHex(runtimeSnapshot, storage) + val storageRawData = storageType.fromHex(runtimeSnapshot, storage) + (storageRawData as? Struct.Instance)?.let { instance -> instance.mapToToken("baseAssetId")?.let { token -> storageKey.takeInt32() to token @@ -462,6 +457,7 @@ class PoolsRepositoryImpl @Inject constructor( } } } + poolBaseTokensDeferred.awaitAll().filterNotNull() } } @@ -675,10 +671,13 @@ class PoolsRepositoryImpl @Inject constructor( @Suppress("UNCHECKED_CAST") fun SubscriptionChange.storageChange(): SubscribeStorageResult { - val result = params.result as? Map<*, *> ?: throw IllegalArgumentException("${params.result} is not a valid storage result") + val result = params.result as? Map<*, *> + ?: throw IllegalArgumentException("${params.result} is not a valid storage result") - val block = result["block"] as? String ?: throw IllegalArgumentException("$result is not a valid storage result") - val changes = result["changes"] as? List> ?: throw IllegalArgumentException("$result is not a valid storage result") + val block = result["block"] as? String + ?: throw IllegalArgumentException("$result is not a valid storage result") + val changes = result["changes"] as? List> + ?: throw IllegalArgumentException("$result is not a valid storage result") return SubscribeStorageResult(block, changes) } @@ -863,73 +862,74 @@ class PoolsRepositoryImpl @Inject constructor( } } - override suspend fun updateAccountPools(chainId: ChainId, address: String) { + override suspend fun updateAccountPools(chainId: ChainId, address: String) = supervisorScope { println("!!! call blockExplorerManager.updateAccountPools() chainId = $chainId") - blockExplorerManager.updatePoolsSbApy() - - val pools = mutableListOf() val assets = chainRegistry.getChain(chainId).assets println("!!! call blockExplorerManager.updateAccountPools() assets = ${assets.size}") val tokenIds = getUserPoolsTokenIds(chainId, address) println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") - tokenIds.forEach { (baseTokenId, tokensId) -> - - val baseToken = assets.firstOrNull { - it.currencyId == baseTokenId - } ?: return@forEach - - val xorPrecision = baseToken.precision - - tokensId.mapNotNull { tokenId -> - getUserPoolData(chainId, address, baseTokenId, tokenId) - }.forEach pool@{ poolDataDto -> - val token = assets.firstOrNull { - it.currencyId == poolDataDto.assetId - } ?: return@pool - val tokenPrecision = token.precision - - val basicPoolLocal = BasicPoolLocal( - tokenIdBase = baseTokenId, - tokenIdTarget = poolDataDto.assetId, - reserveBase = mapBalance( - poolDataDto.reservesFirst, - xorPrecision - ), - reserveTarget = mapBalance( - poolDataDto.reservesSecond, - tokenPrecision - ), - totalIssuance = mapBalance( - poolDataDto.totalIssuance, - xorPrecision - ), - reservesAccount = poolDataDto.reservesAccount, - ) + val poolsDeferred = tokenIds.map { (baseTokenId, tokensId) -> + async { + val baseToken = assets.firstOrNull { + it.currencyId == baseTokenId + } ?: return@async emptyList() + + val xorPrecision = baseToken.precision + + val poolData = tokensId.map { tokenId -> + async { getUserPoolData(chainId, address, baseTokenId, tokenId) } + } - val userPoolLocal = UserPoolLocal( - accountAddress = address, - userTokenIdBase = baseTokenId, - userTokenIdTarget = poolDataDto.assetId, - poolProvidersBalance = mapBalance( - poolDataDto.poolProvidersBalance, - xorPrecision + poolData.awaitAll().filterNotNull().mapNotNull pool@{ poolDataDto -> + val token = assets.firstOrNull { + it.currencyId == poolDataDto.assetId + } ?: return@pool null + val tokenPrecision = token.precision + + val basicPoolLocal = BasicPoolLocal( + tokenIdBase = baseTokenId, + tokenIdTarget = poolDataDto.assetId, + reserveBase = mapBalance( + poolDataDto.reservesFirst, + xorPrecision + ), + reserveTarget = mapBalance( + poolDataDto.reservesSecond, + tokenPrecision + ), + totalIssuance = mapBalance( + poolDataDto.totalIssuance, + xorPrecision + ), + reservesAccount = poolDataDto.reservesAccount, + ) + + val userPoolLocal = UserPoolLocal( + accountAddress = address, + userTokenIdBase = baseTokenId, + userTokenIdTarget = poolDataDto.assetId, + poolProvidersBalance = mapBalance( + poolDataDto.poolProvidersBalance, + xorPrecision + ) ) - ) - pools.add( - UserPoolJoinedLocal( + return@pool UserPoolJoinedLocal( userPoolLocal = userPoolLocal, basicPoolLocal = basicPoolLocal, ) - ) + } } } + val pools = poolsDeferred.awaitAll().flatten() + db.withTransaction { println("!!! updateAccountPools: poolDao.clearTable(address)") poolDao.clearTable(address) + println( "!!! updateAccountPools: poolDao.insertBasicPools() size = ${ pools.map { @@ -957,57 +957,65 @@ class PoolsRepositoryImpl @Inject constructor( } } - override suspend fun updateBasicPools(chainId: ChainId) { + override suspend fun updateBasicPools(chainId: ChainId) = coroutineScope { println("!!! pswapRepo updateBasicPools") - val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) - val storage = runtimeOrNull?.metadata - ?.module(Modules.POOL_XYK) - ?.storage("Reserves") + val runtimeOrNull = chainRegistry.awaitRuntimeProvider(chainId).get() + val storage = runtimeOrNull.metadata + .module(Modules.POOL_XYK) + .storage("Reserves") - val list = mutableListOf() +// val list = mutableListOf() val soraChain = chainRegistry.getChain(chainId) val assets = soraChain.assets - getPoolBaseTokens(chainId).forEach { (dexId, tokenId) -> - val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@forEach - val key = runtimeOrNull?.reservesKeyToken(tokenId) ?: return@forEach - - getStateKeys(chainId, key).forEach { storageKey -> - val targetToken = storageKey.assetIdFromKey() - getStorageHex(chainId, storageKey)?.let { storageHex -> - storage?.type?.value - ?.fromHex(runtimeOrNull, storageHex) - ?.safeCast>()?.let { reserves -> - - val reserveAccount = getPoolReserveAccount( - chainId, - tokenId, - targetToken.fromHex() - ) - - val total = reserveAccount?.let { - getPoolTotalIssuances(chainId, it) - }?.let { - mapBalance(it, asset.precision) - } + val basicPoolsLocalDeferred = getPoolBaseTokens(chainId).map { (dexId, tokenId) -> + async { + val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@async null + val key = runtimeOrNull.reservesKeyToken(tokenId) - val reserveAccountAddress = reserveAccount?.let { soraChain.addressOf(it) } ?: "" - list.add( - BasicPoolLocal( - tokenIdBase = tokenId, - tokenIdTarget = targetToken, - reserveBase = mapBalance(reserves[0], asset.precision), - reserveTarget = mapBalance(reserves[1], asset.precision), - totalIssuance = total ?: BigDecimal.ZERO, - reservesAccount = reserveAccountAddress, - ) - ) + val basicPoolDeferred = getStateKeys(chainId, key).map { storageKey -> + async basicPoolOperation@{ + val targetToken = storageKey.assetIdFromKey() + val storageHex = + getStorageHex(chainId, storageKey) ?: return@basicPoolOperation null + val reserves = storage.type.value + ?.fromHex(runtimeOrNull, storageHex) + ?.safeCast>() ?: return@basicPoolOperation null + + val reserveAccount = getPoolReserveAccount( + chainId, + tokenId, + targetToken.fromHex() + ) + + val total = reserveAccount?.let { + getPoolTotalIssuances(chainId, it) + }?.let { + mapBalance(it, asset.precision) } + + val reserveAccountAddress = + reserveAccount?.let { soraChain.addressOf(it) } ?: "" + + BasicPoolLocal( + tokenIdBase = tokenId, + tokenIdTarget = targetToken, + reserveBase = mapBalance(reserves[0], asset.precision), + reserveTarget = mapBalance( + reserves[1], + asset.precision + ), + totalIssuance = total ?: BigDecimal.ZERO, + reservesAccount = reserveAccountAddress, + ) + } } + basicPoolDeferred.awaitAll().filterNotNull() } } + val list = basicPoolsLocalDeferred.awaitAll().filterNotNull().flatten() val minus = poolDao.getBasicPools().filter { db -> list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null @@ -1017,60 +1025,29 @@ class PoolsRepositoryImpl @Inject constructor( poolDao.insertBasicPools(list) } - val assetsFlow = accountRepository.selectedMetaAccountFlow() - .distinctUntilChanged { old, new -> old.id != new.id } - .flatMapLatest { meta -> - walletRepository.assetsFlow(meta) - }.mapList { it.asset } - - @OptIn(FlowPreview::class) - override fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow { - return combine( - poolDao.subscribePool(address, baseTokenId, targetTokenId), - assetsFlow - ) { pool, assets -> + private suspend fun getAssets(): List { + return chainRegistry.getChain(poolsChainId).assets + } + + override fun subscribePool( + address: String, + baseTokenId: String, + targetTokenId: String + ): Flow { + return poolDao.subscribePool(address, baseTokenId, targetTokenId).map { pool -> + val assets = getAssets() mapPoolLocalToData(pool, assets) } .mapNotNull { it } - .debounce(500) - -// return poolDao.subscribePool(address, baseTokenId, targetTokenId).mapNotNull { -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// mapPoolLocalToData(it, assets) -// }.debounce(500) } - @OptIn(FlowPreview::class) override fun subscribePools(address: String): Flow> { - println("!!! repoImpl call subscribePools for address: $address") - - return combine( - poolDao.subscribeAllPools(address), - assetsFlow - ) { pools, assets -> - println("!!! repoImpl subscribePools pools: ${pools.size}") - val mapNotNull = pools.mapNotNull { poolLocal -> + return poolDao.subscribeAllPools(address).map { pools -> + val assets = getAssets() + pools.mapNotNull { poolLocal -> mapPoolLocalToData(poolLocal, assets) } - println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") - mapNotNull } - .debounce(500) - -// return poolDao.subscribeAllPools(address).map { pools -> -// println("!!! repoImpl subscribePools pools: ${pools.size}") -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// val mapNotNull = pools.mapNotNull { poolLocal -> -// mapPoolLocalToData(poolLocal, assets) -// } -// println("!!! repoImpl subscribePools mapNotNull pools: ${mapNotNull.size}") -// mapNotNull -// }.debounce(500) - .onEach { - println("!!! repoImpl .debounce(500) pools: ${it.size}") - } } fun RuntimeSnapshot.accountPoolsKey(address: String): String = @@ -1080,13 +1057,13 @@ class PoolsRepositoryImpl @Inject constructor( private fun mapPoolLocalToData( poolLocal: UserPoolJoinedLocalNullable, - assets: List + assets: List ): CommonPoolData? { val baseToken = assets.firstOrNull { - it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdBase + it.currencyId == poolLocal.basicPoolLocal.tokenIdBase } ?: return null val token = assets.firstOrNull { - it.token.configuration.currencyId == poolLocal.basicPoolLocal.tokenIdTarget + it.currencyId == poolLocal.basicPoolLocal.tokenIdTarget } ?: return null val basicPoolData = BasicPoolData( @@ -1104,13 +1081,13 @@ class PoolsRepositoryImpl @Inject constructor( poolLocal.basicPoolLocal.reserveBase, userPoolLocal.poolProvidersBalance, poolLocal.basicPoolLocal.totalIssuance, - baseToken.token.configuration.precision, + baseToken.precision, ) val secondPooled = PolkaswapFormulas.calculatePooledValue( poolLocal.basicPoolLocal.reserveTarget, userPoolLocal.poolProvidersBalance, poolLocal.basicPoolLocal.totalIssuance, - token.token.configuration.precision, + token.precision, ) val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( userPoolLocal.poolProvidersBalance, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index 5b05a2bb0c..ad5819ceab 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -4,8 +4,6 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import javax.inject.Named -import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.core.extrinsic.ExtrinsicService @@ -22,14 +20,11 @@ import jp.co.soramitsu.liquiditypools.impl.domain.DemeterFarmingInteractorImpl import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter -import jp.co.soramitsu.polkaswap.api.data.PolkaswapRepository -import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager -import jp.co.soramitsu.runtime.di.REMOTE_STORAGE_SOURCE import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.runtime.storage.source.StorageDataSource import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -41,9 +36,10 @@ class PoolsModule { poolsRepository: PoolsRepository, accountRepository: AccountRepository, chainRegistry: ChainRegistry, - keypairProvider: KeypairProvider + keypairProvider: KeypairProvider, + blockExplorerManager: BlockExplorerManager ): PoolsInteractor = - PoolsInteractorImpl(poolsRepository, accountRepository, chainRegistry, keypairProvider) + PoolsInteractorImpl(poolsRepository, accountRepository, chainRegistry, keypairProvider, blockExplorerManager) @Provides @Singleton diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index ed9fe72254..eade2bc1e8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.domain -import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.data.secrets.v1.Keypair @@ -14,19 +13,29 @@ import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext +import java.math.BigDecimal +import kotlin.coroutines.CoroutineContext class PoolsInteractorImpl( private val poolsRepository: PoolsRepository, private val accountRepository: AccountRepository, private val chainRegistry: ChainRegistry, private val keypairProvider: KeypairProvider, + private val blockExplorerManager: BlockExplorerManager, + private val coroutineContext: CoroutineContext = Dispatchers.Default ) : PoolsInteractor { override val poolsChainId = poolsRepository.poolsChainId @@ -40,7 +49,7 @@ class PoolsInteractorImpl( // } override fun subscribePoolsCacheOfAccount(address: String): Flow> { - return poolsRepository.subscribePools(address) + return poolsRepository.subscribePools(address).flowOn(coroutineContext) } private val soraPoolsAddressFlow = flowOf { @@ -224,10 +233,14 @@ class PoolsInteractorImpl( return status?.getOrNull() ?: "" } - override suspend fun updatePools(chainId: ChainId) { + override suspend fun syncPools(chainId: ChainId): Unit = withContext(Dispatchers.Default) { val address = accountRepository.getSelectedAccount(chainId).address - poolsRepository.updateAccountPools(chainId, address) + supervisorScope { + launch { blockExplorerManager.updatePoolsSbApy() } - poolsRepository.updateBasicPools(chainId) + launch { poolsRepository.updateAccountPools(chainId, address) } + + launch { poolsRepository.updateBasicPools(chainId) } + } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 567a76aa26..4af04b885f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -1,9 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.presentation -import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.base.BaseViewModel @@ -41,8 +38,8 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.NavAction -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted @@ -50,6 +47,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject @HiltViewModel class PoolsFlowViewModel @Inject constructor( @@ -81,7 +80,7 @@ class PoolsFlowViewModel @Inject constructor( } val allPoolsScreenState: StateFlow = - allPoolsPresenter.createScreenStateFlow(coroutinesStore.uiScope) + allPoolsPresenter.createScreenStateFlow() val poolListScreenState: StateFlow = poolListPresenter.createScreenStateFlow(coroutinesStore.uiScope) @@ -119,7 +118,7 @@ class PoolsFlowViewModel @Inject constructor( internalPoolsRouter.openAllPoolsScreen() launch { - poolsInteractor.updatePools(poolsInteractor.poolsChainId) + poolsInteractor.syncPools(poolsInteractor.poolsChainId) } } @@ -193,23 +192,23 @@ class PoolsFlowViewModel @Inject constructor( } } -fun CommonPoolData.toListItemState(): BasicPoolListItemState? { - val tvl = basic.baseToken.token.fiatRate?.times(BigDecimal(2)) +fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { + val tvl = baseToken?.fiatRate?.times(BigDecimal(2)) ?.multiply(basic.baseReserves) - ?.formatFiat(basic.baseToken.token.fiatSymbol).orEmpty() + ?.formatFiat(baseToken.fiatSymbol).orEmpty() - val baseTokenId = basic.baseToken.token.configuration.currencyId ?: return null - val targetTokenId = basic.targetToken?.token?.configuration?.currencyId ?: return null + val baseTokenId = basic.baseToken.currencyId ?: return null + val targetTokenId = basic.targetToken?.currencyId ?: return null - val baseSymbol = basic.baseToken.token.configuration.symbol - val targetSymbol = basic.targetToken?.token?.configuration?.symbol + val baseSymbol = basic.baseToken.symbol + val targetSymbol = basic.targetToken?.symbol val userPooledInfo = user?.let { "${it.basePooled.formatCrypto(baseSymbol)} - ${it.targetPooled.formatCrypto(targetSymbol)}" } val text2Color = if (user == null) white50 else user.let { greenText } return BasicPoolListItemState( ids = StringPair(baseTokenId, targetTokenId), - token1Icon = basic.baseToken.token.configuration.iconUrl, - token2Icon = basic.targetToken?.token?.configuration?.iconUrl.orEmpty(), + token1Icon = basic.baseToken.iconUrl, + token2Icon = basic.targetToken?.iconUrl.orEmpty(), text1 = "$baseSymbol-$targetSymbol".uppercase(), text2 = userPooledInfo ?: tvl, text2Color = text2Color, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 021dfdfcfa..014f2c2454 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -1,34 +1,28 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc -import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import kotlin.math.max -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject class AllPoolsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -43,82 +37,70 @@ class AllPoolsPresenter @Inject constructor( .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - -// val pools = flowOf { poolsInteractor.getBasicPools() }.map { pools -> -// pools.sortedWith { o1, o2 -> -// compareNullDesc(o1.tvl, o2.tvl) -// } -// }.map { -// it.map(BasicPoolData::toListItemState) -// } - - private val chainFlow = flowOf { + private val chainDeferred = coroutinesStore.uiScope.async { chainsRepository.getChain(poolsInteractor.poolsChainId) } - @OptIn(ExperimentalCoroutinesApi::class) - val allPools = combine( - accountInteractor.selectedMetaAccountFlow(), - chainFlow - ) { wallet, chain -> - wallet.address(chain) + init { + loadPoolData() } - .mapNotNull { it } - .distinctUntilChanged() - .flatMapLatest { address -> - println("!!! allPools flatMapLatest address = $address") - poolsInteractor.subscribePoolsCacheOfAccount(address) - }.map { pools -> - println("!!! allPools subscribePoolsCacheOfAccount pools.size = ${pools.size}") - pools.groupBy { - it.user != null - }.mapValues { - it.value.sortedWith { o1, o2 -> - compareNullDesc(o1.basic.tvl, o2.basic.tvl) - } - } - }.map { - println("!!! allPools grouped users = ${it[true]?.size}; other = ${it[false]?.size}") -// it.map(BasicPoolData::toListItemState) - it.mapValues { it -> - it.value.mapNotNull { it.toListItemState() } - } - } - init { + private fun loadPoolData() { + coroutinesStore.ioScope.launch { + val currentAccount = accountInteractor.selectedMetaAccount() + val address = currentAccount.address(chainDeferred.await()) ?: return@launch + poolsInteractor.subscribePoolsCacheOfAccount(address) + .onEach { commonPoolData: List -> + val allRequiredChainAssets = + (commonPoolData.map { it.basic.baseToken } + commonPoolData.mapNotNull { it.basic.targetToken }).toSet() + + val tokensDeferred = + allRequiredChainAssets.filter { it.priceId != null }.map { walletInteractor.getToken(it) } + + val tokensMap = tokensDeferred.associateBy { it.configuration.id } + + val poolLists = commonPoolData.groupBy { + it.user != null + }.mapValues { + it.value.sortedWith { current, next -> + val currentTvl = + current.basic.getTvl(tokensMap[current.basic.baseToken.id]?.fiatRate) + val nextTvl = + next.basic.getTvl(tokensMap[next.basic.baseToken.id]?.fiatRate) + compareNullDesc(currentTvl, nextTvl) + }.mapNotNull { commonPoolData -> + val token = tokensMap[commonPoolData.basic.baseToken.id] + commonPoolData.toListItemState(token) + } + } + val userPools = poolLists[true].orEmpty() + val otherPools = poolLists[false].orEmpty() + + val shownUserPools = userPools.take(5) + val shownOtherPools = otherPools.take(10) + val hasExtraUserPools = shownUserPools.size < userPools.size + val hasExtraAllPools = shownOtherPools.size < otherPools.size + + stateFlow.update { prevState -> + prevState.copy( + userPools = shownUserPools, + allPools = shownOtherPools, + hasExtraUserPools = hasExtraUserPools, + hasExtraAllPools = hasExtraAllPools, + isLoading = false + ) + } + }.launchIn(coroutinesStore.uiScope) + } } + private val stateFlow = MutableStateFlow(AllPoolsState()) - fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { - subscribeState(coroutineScope) + fun createScreenStateFlow(): StateFlow { return stateFlow } - private fun subscribeState(coroutineScope: CoroutineScope) { - allPools.onEach { poolLists -> - println("!!! allPools subscribeState poolLists.size = ${poolLists.size}") - - val userPools = poolLists[true].orEmpty() - val otherPools = poolLists[false].orEmpty() - - val shownUserPools = userPools.take(5) - val shownOtherPools = otherPools.take(10) - - val hasExtraUserPools = shownUserPools.size < userPools.size - val hasExtraAllPools = shownOtherPools.size < otherPools.size - - stateFlow.value = stateFlow.value.copy( - userPools = shownUserPools, - allPools = shownOtherPools, - hasExtraUserPools = hasExtraUserPools, - hasExtraAllPools = hasExtraAllPools, - isLoading = false - ) - }.launchIn(coroutineScope) - } - - override fun onPoolClicked(pair: StringPair) { internalPoolsRouter.openDetailsPoolScreen(pair) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 42cdae7c3e..52133e75b9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.isZero import jp.co.soramitsu.common.base.errors.ValidationException @@ -31,10 +28,11 @@ import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -46,6 +44,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn @@ -54,6 +53,10 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import kotlin.math.min @OptIn(FlowPreview::class) class LiquidityRemovePresenter @Inject constructor( @@ -104,33 +107,20 @@ class LiquidityRemovePresenter @Inject constructor( ) } - @OptIn(ExperimentalCoroutinesApi::class) - val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> - val ids = screenArgs.ids + private val baseToTargetTokensFlow = screenArgsFlow.mapNotNull { screenArgs -> + val currencyIds = screenArgs.ids val chainId = poolsInteractor.poolsChainId - println("!!! assetsInPoolFlow ids = $ids") - val assetsFlow = walletInteractor.assetsFlow().mapNotNull { - val firstInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.first - && it.asset.token.configuration.chainId == chainId - } - val secondInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.second - && it.asset.token.configuration.chainId == chainId - } - println("!!! assetsInPoolFlow result% $firstInPair; $secondInPair") - if (firstInPair == null || secondInPair == null) { - return@mapNotNull null - } else { - firstInPair to secondInPair - } - } - assetsFlow - }.distinctUntilChanged() + val chain = chainsRepository.getChain(chainId) + val first = chain.assets.find { it.currencyId == currencyIds.first } ?: return@mapNotNull null + val second = chain.assets.find { it.currencyId == currencyIds.second } ?: return@mapNotNull null + + coroutineScope { + val baseTokenDeferred = async { walletInteractor.getToken(first) } + val targetTokenDeferred = async { walletInteractor.getToken(second) } - private val tokensInPoolFlow = assetsInPoolFlow.map { - it.first.asset.token.configuration to it.second.asset.token.configuration + baseTokenDeferred.await() to targetTokenDeferred.await() + } }.distinctUntilChanged() @OptIn(ExperimentalCoroutinesApi::class) @@ -183,17 +173,17 @@ class LiquidityRemovePresenter @Inject constructor( basePooled = PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.basePooled, usablePercent, - poolDataLocal.basic.baseToken.token.configuration.precision, + poolDataLocal.basic.baseToken.precision, ), targetPooled = PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.targetPooled, usablePercent, - poolDataLocal.basic.baseToken.token.configuration.precision, + poolDataLocal.basic.baseToken.precision, ), poolProvidersBalance = PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.poolProvidersBalance, usablePercent, - poolDataLocal.basic.baseToken.token.configuration.precision, + poolDataLocal.basic.baseToken.precision, ), ) ) @@ -214,13 +204,13 @@ class LiquidityRemovePresenter @Inject constructor( if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.basePooled, percent, - poolDataLocal.basic.baseToken.token.configuration.precision, + poolDataLocal.basic.baseToken.precision, ) else BigDecimal.ZERO amountTarget = if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.targetPooled, percent, - poolDataLocal.basic.targetToken?.token?.configuration?.precision!!, + poolDataLocal.basic.targetToken?.precision!!, ) else BigDecimal.ZERO coroutinesStore.ioScope.launch { @@ -232,17 +222,16 @@ class LiquidityRemovePresenter @Inject constructor( @OptIn(FlowPreview::class) private fun subscribeState(coroutineScope: CoroutineScope) { - poolDataFlow.onEach { - val baseToken = it.basic.baseToken.token - val targetToken = it.basic.targetToken?.token + poolDataFlow.onEach { pool -> + val (baseToken, targetToken) = baseToTargetTokensFlow.first() - val pooledBaseCrypto = it.user?.basePooled?.formatCrypto(baseToken.configuration.symbol).orEmpty() - val pooledBaseFiat = it.user?.basePooled?.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol) + val pooledBaseCrypto = pool.user?.basePooled?.formatCrypto(baseToken.configuration.symbol).orEmpty() + val pooledBaseFiat = pool.user?.basePooled?.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol) val argsBase = pooledBaseCrypto + pooledBaseFiat?.let { " ($it)" }.orEmpty() val pooledBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) - val pooledTargetCrypto = it.user?.targetPooled?.formatCrypto(targetToken?.configuration?.symbol).orEmpty() - val pooledTargetFiat = it.user?.targetPooled?.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol) + val pooledTargetCrypto = pool.user?.targetPooled?.formatCrypto(targetToken.configuration.symbol).orEmpty() + val pooledTargetFiat = pool.user?.targetPooled?.applyFiatRate(targetToken.fiatRate)?.formatFiat(targetToken.fiatSymbol) val argsTarget = pooledTargetCrypto + pooledTargetFiat?.let { " ($it)" }.orEmpty() val pooledTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) @@ -253,8 +242,8 @@ class LiquidityRemovePresenter @Inject constructor( totalBalance = pooledBaseBalance, ), targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - tokenName = targetToken?.configuration?.symbol, - tokenImage = targetToken?.configuration?.iconUrl, + tokenName = targetToken.configuration.symbol, + tokenImage = targetToken.configuration.iconUrl, totalBalance = pooledTargetBalance, ) ) @@ -268,10 +257,11 @@ class LiquidityRemovePresenter @Inject constructor( }.launchIn(coroutineScope) enteredBaseAmountFlow.onEach { - val baseToken = assetsInPoolFlow.firstOrNull()?.first?.asset?.token + val (baseToken, _) = baseToTargetTokensFlow.first() + stateFlow.value = stateFlow.value.copy( baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - fiatAmount = it.applyFiatRate(baseToken?.fiatRate)?.formatFiat(baseToken?.fiatSymbol), + fiatAmount = it.applyFiatRate(baseToken.fiatRate)?.formatFiat(baseToken.fiatSymbol), tokenAmount = it, ) ) @@ -281,7 +271,7 @@ class LiquidityRemovePresenter @Inject constructor( poolDataUsable?.let { amountBase = if (it.user.basePooled <= amount) amount else it.user.basePooled - val precisionTo = poolDataFlow.firstOrNull()?.basic?.targetToken?.token?.configuration?.precision + val precisionTo = poolDataFlow.firstOrNull()?.basic?.targetToken?.precision amountTarget = PolkaswapFormulas.calculateOneAmountFromAnother( amountBase, it.user.basePooled, @@ -301,11 +291,11 @@ class LiquidityRemovePresenter @Inject constructor( enteredTargetAmountFlow.onEach { println("!!! enteredToAmountFlow.onEach = $it") + val (_, targetToken) = baseToTargetTokensFlow.first() - val targetToken = assetsInPoolFlow.firstOrNull()?.second?.asset?.token stateFlow.value = stateFlow.value.copy( targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - fiatAmount = it.applyFiatRate(targetToken?.fiatRate)?.formatFiat(targetToken?.fiatSymbol), + fiatAmount = it.applyFiatRate(targetToken.fiatRate)?.formatFiat(targetToken.fiatSymbol), tokenAmount = it ), ) @@ -316,7 +306,7 @@ class LiquidityRemovePresenter @Inject constructor( poolDataUsable?.let { amountTarget = if (amount <= it.user.targetPooled) amount else it.user.targetPooled - val precisionBase = poolDataFlow.firstOrNull()?.basic?.baseToken?.token?.configuration?.precision + val precisionBase = poolDataFlow.firstOrNull()?.basic?.baseToken?.precision amountBase = PolkaswapFormulas.calculateOneAmountFromAnother( amountTarget, it.user.targetPooled, @@ -367,7 +357,7 @@ class LiquidityRemovePresenter @Inject constructor( } private suspend fun updateAmounts() { - assetsInPoolFlow.firstOrNull()?.let { (assetBase, assetTarget) -> + baseToTargetTokensFlow.firstOrNull()?.let { (tokenBase, tokenTarget) -> if (amountBase.compareTo(stateFlow.value.baseAmountInputViewState.tokenAmount) != 0) { println("!!! updateAmounts amountFrom to $amountBase") @@ -382,7 +372,7 @@ class LiquidityRemovePresenter @Inject constructor( stateFlow.value = stateFlow.value.copy( baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( tokenAmount = scaledAmountBase, - fiatAmount = amountBase.applyFiatRate(assetBase.asset.token.fiatRate)?.formatFiat(assetBase.asset.token.fiatSymbol), + fiatAmount = amountBase.applyFiatRate(tokenBase.fiatRate)?.formatFiat(tokenTarget.fiatSymbol), ) ) } @@ -398,7 +388,7 @@ class LiquidityRemovePresenter @Inject constructor( stateFlow.value = stateFlow.value.copy( targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( tokenAmount = scaledAmountTarget, - fiatAmount = amountTarget.applyFiatRate(assetTarget.asset.token.fiatRate)?.formatFiat(assetTarget.asset.token.fiatSymbol), + fiatAmount = amountTarget.applyFiatRate(tokenTarget.fiatRate)?.formatFiat(tokenTarget.fiatSymbol), ) ) } @@ -414,10 +404,10 @@ class LiquidityRemovePresenter @Inject constructor( ) } - private val networkFeeFlow = tokensInPoolFlow.map { (baseAsset, targetAsset) -> + private val networkFeeFlow = baseToTargetTokensFlow.map { (baseToken, targetToken) -> val networkFee = getRemoveLiquidityNetworkFee( - tokenBase = baseAsset, - tokenTarget = targetAsset, + tokenBase = baseToken.configuration, + tokenTarget = targetToken.configuration, ) println("!!!! RemoveLiquidity FeeFlow emit $networkFee") networkFee @@ -474,7 +464,7 @@ class LiquidityRemovePresenter @Inject constructor( val utilityAmount = utilityAssetFlow.firstOrNull()?.transferable ?: return@launch val feeAmount = networkFeeFlow.firstOrNull().orZero() - val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch + val poolAssets = baseToTargetTokensFlow.first() val userBasePooled = poolDataReal?.user?.basePooled ?: return@launch val userTargetPooled = poolDataReal?.user?.targetPooled ?: return@launch @@ -518,7 +508,7 @@ class LiquidityRemovePresenter @Inject constructor( PolkaswapFormulas.calculateAmountByPercentage( poolDataUsable?.user?.poolProvidersBalance.orZero(), percent, - poolAssets.first.asset.token.configuration.precision + poolAssets.first.configuration.precision ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index eb8057cc07..85a4d2fcd6 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair @@ -16,6 +15,7 @@ import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import javax.inject.Inject class PoolDetailsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -56,9 +57,10 @@ class PoolDetailsPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { - screenArgsFlow.flatMapLatest { - observePoolDetails(it.ids).onEach { - stateFlow.value = it.mapToState() + screenArgsFlow.flatMapLatest { args -> + observePoolDetails(args.ids).onEach { pool -> + val token = walletInteractor.getToken(pool.basic.baseToken) + stateFlow.value = pool.mapToState(token) } // requestPoolDetails(it.ids)?.let { // stateFlow.value = it @@ -113,15 +115,16 @@ class PoolDetailsPresenter @Inject constructor( } } -private fun CommonPoolData.mapToState(): PoolDetailsState { +private fun CommonPoolData.mapToState(token: Token): PoolDetailsState { + val tvl = basic.getTvl(token.fiatRate) return PoolDetailsState( - assetBase = basic.baseToken.token.configuration, - assetTarget = basic.targetToken?.token?.configuration, - pooledBaseAmount = user?.basePooled?.formatCrypto(basic.baseToken.token.configuration.symbol).orEmpty(), - pooledBaseFiat = user?.basePooled?.applyFiatRate(basic.baseToken.token.fiatRate)?.formatFiat(basic.baseToken.token.fiatSymbol).orEmpty(), - pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.token?.configuration?.symbol).orEmpty(), - pooledTargetFiat = user?.targetPooled?.applyFiatRate(basic.targetToken?.token?.fiatRate)?.formatFiat(basic.targetToken?.token?.fiatSymbol).orEmpty(), - tvl = basic.tvl?.formatFiat(basic.baseToken.token.fiatSymbol), + assetBase = basic.baseToken, + assetTarget = basic.targetToken, + pooledBaseAmount = user?.basePooled?.formatCrypto(basic.baseToken.symbol).orEmpty(), + pooledBaseFiat = user?.basePooled?.applyFiatRate(token.fiatRate)?.formatFiat(token.fiatSymbol).orEmpty(), + pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.symbol).orEmpty(), + pooledTargetFiat = user?.targetPooled?.applyFiatRate(token.fiatRate)?.formatFiat(token.fiatSymbol).orEmpty(), + tvl = tvl?.formatFiat(token.fiatSymbol), apy = "${basic.sbapy?.toBigDecimal()?.formatPercent()}%" ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 1773e6285f..8aad5c7992 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.poollist -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc @@ -13,6 +12,9 @@ import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -20,9 +22,9 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import javax.inject.Inject class PoolListPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -42,24 +44,31 @@ class PoolListPresenter @Inject constructor( } .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) - val pools = screenArgsFlow.flatMapLatest { screenArgs -> + private val pools = screenArgsFlow.flatMapLatest { screenArgs -> combine( poolsInteractor.subscribePoolsCacheCurrentAccount(), enteredAssetQueryFlow ) { pools, query -> - pools.filter { - if (screenArgs.isUserPools) { - it.user != null - } else { - true - } - }.filter { - it.basic.isFilterMatch(query) - }.sortedWith { o1, o2 -> - compareNullDesc(o1.basic.tvl, o2.basic.tvl) + coroutineScope { + + val tokensDeferred = + pools.map { async { walletInteractor.getToken(it.basic.baseToken) } } + val tokensMap = tokensDeferred.awaitAll().associateBy { it.configuration.id } + + pools.filter { + if (screenArgs.isUserPools) { + it.user != null + } else { + true + } + }.filter { + it.basic.isFilterMatch(query) + }.sortedWith { current, next -> + val currentTvl = current.basic.getTvl(tokensMap[current.basic.baseToken.id]?.fiatRate) + val nextTvl = next.basic.getTvl(tokensMap[next.basic.baseToken.id]?.fiatRate) + compareNullDesc(currentTvl, nextTvl) + }.mapNotNull { it.toListItemState(tokensMap[it.basic.baseToken.id]) } } - }.map { - it.mapNotNull{ it.toListItemState() } } } diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt index f26740856a..049622c2a1 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.polkaswap.api.domain.models +import jp.co.soramitsu.core.models.Asset import java.math.BigDecimal -import jp.co.soramitsu.wallet.impl.domain.model.Asset data class BasicPoolData( val baseToken: Asset, @@ -12,6 +12,7 @@ data class BasicPoolData( val reserveAccount: String, val sbapy: Double?, ) { - val tvl: BigDecimal? - get() = baseToken.token.fiatRate?.times(BigDecimal(2))?.multiply(baseReserves) + fun getTvl(baseTokenFiatRate: BigDecimal?): BigDecimal? { + return baseTokenFiatRate?.times(BigDecimal(2))?.multiply(baseReserves) + } } \ No newline at end of file diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt index 8df483eb71..6185b8a76f 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt @@ -23,11 +23,11 @@ data class UserPoolData( fun BasicPoolData.isFilterMatch(filter: String): Boolean { val t1 = // targetToken?.token?.configuration?.name?.lowercase()?.contains(filter.lowercase()) == true || - targetToken?.token?.configuration?.symbol?.lowercase()?.contains(filter.lowercase()) == true || - targetToken?.token?.configuration?.currencyId?.lowercase()?.contains(filter.lowercase()) == true + targetToken?.symbol?.lowercase()?.contains(filter.lowercase()) == true || + targetToken?.currencyId?.lowercase()?.contains(filter.lowercase()) == true val t2 = // baseToken.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || - baseToken.token.configuration.symbol.lowercase().contains(filter.lowercase()) || - baseToken.token.configuration.currencyId?.lowercase()?.contains(filter.lowercase()) == true + baseToken.symbol.lowercase().contains(filter.lowercase()) || + baseToken.currencyId?.lowercase()?.contains(filter.lowercase()) == true return t1 || t2 } diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt index 21562b9277..b80f1fab36 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.wallet.impl.domain.interfaces -import java.io.File -import java.math.BigDecimal -import java.math.BigInteger import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount import jp.co.soramitsu.common.data.model.CursorPage @@ -26,10 +23,14 @@ import jp.co.soramitsu.wallet.impl.domain.model.OperationsPageChange import jp.co.soramitsu.wallet.impl.domain.model.PhishingModel import jp.co.soramitsu.wallet.impl.domain.model.QrContentCBDC import jp.co.soramitsu.wallet.impl.domain.model.QrContentSora +import jp.co.soramitsu.wallet.impl.domain.model.Token import jp.co.soramitsu.wallet.impl.domain.model.Transfer import jp.co.soramitsu.wallet.impl.domain.model.TransferValidityStatus import jp.co.soramitsu.wallet.impl.domain.model.WalletAccount import kotlinx.coroutines.flow.Flow +import java.io.File +import java.math.BigDecimal +import java.math.BigInteger import jp.co.soramitsu.core.models.Asset as CoreAsset class NotValidTransferStatus(val status: TransferValidityStatus) : Exception() @@ -164,4 +165,5 @@ interface WalletInteractor { fun observeCurrentAccountChainsPerAsset(assetId: String): Flow> suspend fun getOperationAddressWithChainId(chainId: ChainId, limit: Int?): Set + suspend fun getToken(chainAsset: jp.co.soramitsu.core.models.Asset): Token } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt index acdb486929..771ec6e928 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt @@ -8,8 +8,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Named -import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.impl.domain.WalletSyncService @@ -97,6 +95,8 @@ import jp.co.soramitsu.xcm.XcmService import jp.co.soramitsu.xcm.domain.XcmEntitiesFetcher import jp.co.soramitsu.xnetworking.basic.networkclient.SoramitsuNetworkClient import jp.co.soramitsu.xnetworking.fearlesswallet.txhistory.client.TxHistoryClientForFearlessWalletFactory +import javax.inject.Named +import javax.inject.Singleton @InstallIn(SingletonComponent::class) @Module @@ -263,7 +263,8 @@ class WalletFeatureModule { updatesMixin: UpdatesMixin, xcmEntitiesFetcher: XcmEntitiesFetcher, chainsRepository: ChainsRepository, - networkStateService: NetworkStateService + networkStateService: NetworkStateService, + tokenRepository: TokenRepository ): WalletInteractor = WalletInteractorImpl( walletRepository, addressBookRepository, @@ -276,7 +277,8 @@ class WalletFeatureModule { updatesMixin, xcmEntitiesFetcher, chainsRepository, - networkStateService + networkStateService, + tokenRepository ) @Provides diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt index 2d683d8ad0..cbb4172bed 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt @@ -4,9 +4,6 @@ import android.net.Uri import android.util.Log import com.mastercard.mpqr.pushpayment.model.PushPaymentData import com.mastercard.mpqr.pushpayment.parser.Parser -import java.math.BigDecimal -import java.math.BigInteger -import java.net.URLDecoder import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.LightMetaAccount import jp.co.soramitsu.account.api.domain.model.MetaAccount @@ -46,6 +43,7 @@ import jp.co.soramitsu.wallet.impl.data.repository.HistoryRepository import jp.co.soramitsu.wallet.impl.data.repository.isSupported import jp.co.soramitsu.wallet.impl.domain.interfaces.AddressBookRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.AssetSorting +import jp.co.soramitsu.wallet.impl.domain.interfaces.TokenRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository @@ -62,7 +60,6 @@ import jp.co.soramitsu.wallet.impl.domain.model.Transfer import jp.co.soramitsu.wallet.impl.domain.model.WalletAccount import jp.co.soramitsu.wallet.impl.domain.model.toPhishingModel import jp.co.soramitsu.xcm.domain.XcmEntitiesFetcher -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -76,6 +73,10 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.withIndex import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import java.math.BigDecimal +import java.math.BigInteger +import java.net.URLDecoder +import kotlin.coroutines.CoroutineContext import jp.co.soramitsu.core.models.Asset as CoreAsset private const val QR_PREFIX_SUBSTRATE = "substrate" @@ -102,6 +103,7 @@ class WalletInteractorImpl( private val xcmEntitiesFetcher: XcmEntitiesFetcher, private val chainsRepository: ChainsRepository, private val networkStateService: NetworkStateService, + private val tokenRepository: TokenRepository, private val coroutineContext: CoroutineContext = Dispatchers.Default ) : WalletInteractor, UpdatesProviderUi by updatesMixin { @@ -697,4 +699,8 @@ class WalletInteractorImpl( } } } + + override suspend fun getToken(chainAsset: jp.co.soramitsu.core.models.Asset) = withContext(coroutineContext) { + tokenRepository.getToken(chainAsset) + } } \ No newline at end of file From 76bcd668a194bf4c012d665e73897fb419db5576 Mon Sep 17 00:00:00 2001 From: Deneath Date: Thu, 15 Aug 2024 14:43:32 +0700 Subject: [PATCH 51/84] fixed data loading, fixed shimmers Signed-off-by: Deneath --- .../liquiditypools/data/PoolsRepository.kt | 9 +- .../domain/interfaces/PoolsInteractor.kt | 6 +- .../impl/data/PoolsRepositoryImpl.kt | 18 +-- .../liquiditypools/impl/di/PoolsModule.kt | 2 - .../impl/domain/PoolsInteractorImpl.kt | 30 +--- .../impl/presentation/PoolsFlowViewModel.kt | 14 +- .../allpools/AllPoolsPresenter.kt | 66 ++++++-- .../presentation/allpools/AllPoolsScreen.kt | 7 +- .../allpools/BasicPoolListItem.kt | 145 ++++++++++++------ .../liquidityadd/LiquidityAddPresenter.kt | 43 +----- .../pooldetails/PoolDetailsPresenter.kt | 16 +- .../pooldetails/PoolDetailsScreen.kt | 13 +- .../poollist/PoolListPresenter.kt | 56 +++++-- .../presentation/poollist/PoolListScreen.kt | 7 +- .../api/domain/models/BasicPoolData.kt | 3 +- .../sorablockexplorer/BlockExplorerManager.kt | 66 +++++--- 16 files changed, 298 insertions(+), 203 deletions(-) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt index 38b8c81c4f..4237dce31d 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -1,18 +1,13 @@ package jp.co.soramitsu.liquiditypools.data -import java.math.BigDecimal -import java.math.BigInteger import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.core.runtime.models.responses.QuoteResponse import jp.co.soramitsu.polkaswap.api.data.PoolDataDto import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.polkaswap.api.models.Market -import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraTestChainId import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import kotlinx.coroutines.flow.Flow +import java.math.BigDecimal interface PoolsRepository { @@ -56,7 +51,7 @@ interface PoolsRepository { suspend fun getPoolBaseTokenDexId(chainId: ChainId, tokenId: String?): Int - fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? + suspend fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? suspend fun observeRemoveLiquidity( chainId: ChainId, diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 8285cef3f0..bd8f3730c0 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -51,10 +51,6 @@ interface PoolsInteractor { targetTokenId: String ): Boolean -// suspend fun updateApy() - - fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? - suspend fun observeAddLiquidity( chainId: ChainId, tokenBase: Asset, @@ -77,4 +73,6 @@ interface PoolsInteractor { secondAmountMin: BigDecimal, networkFee: BigDecimal ): String + + suspend fun getSbApy(id: String): Double? } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index b9eb1ac376..ab86cdf4ca 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -57,7 +57,6 @@ import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange -import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import kotlinx.coroutines.async @@ -79,7 +78,6 @@ class PoolsRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, private val chainRegistry: ChainRegistry, private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository, private val blockExplorerManager: BlockExplorerManager, private val poolDao: PoolDao, private val db: AppDatabase @@ -194,8 +192,8 @@ class PoolsRepositoryImpl @Inject constructor( } } - override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { - val tempApy = blockExplorerManager.getTempApy(reserveAccountOfPool) + override suspend fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? { + val tempApy = blockExplorerManager.getApy(reserveAccountOfPool) return tempApy } @@ -221,8 +219,7 @@ class PoolsRepositoryImpl @Inject constructor( baseReserves = poolLocal.reserveBase, targetReserves = poolLocal.reserveTarget, totalIssuance = poolLocal.totalIssuance, - reserveAccount = poolLocal.reservesAccount, - sbapy = getPoolStrategicBonusAPY(poolLocal.reservesAccount) + reserveAccount = poolLocal.reservesAccount ) } @@ -276,8 +273,7 @@ class PoolsRepositoryImpl @Inject constructor( baseReserves = mapBalance(reserves[0], asset.precision), targetReserves = mapBalance(reserves[1], asset.precision), totalIssuance = total ?: BigDecimal.ZERO, - reserveAccount = reserveAccountAddress, - sbapy = getPoolStrategicBonusAPY(reserveAccountAddress) + reserveAccount = reserveAccountAddress ) println("!!! getBasicPools() list.add(BasicPoolData: $element") @@ -974,7 +970,6 @@ class PoolsRepositoryImpl @Inject constructor( val asset = assets.firstOrNull { it.currencyId == tokenId } ?: return@async null val key = runtimeOrNull.reservesKeyToken(tokenId) - val basicPoolDeferred = getStateKeys(chainId, key).map { storageKey -> async basicPoolOperation@{ val targetToken = storageKey.assetIdFromKey() @@ -1055,7 +1050,7 @@ class PoolsRepositoryImpl @Inject constructor( .storage("AccountPools") .storageKey(this, address.toAccountId()) - private fun mapPoolLocalToData( + private suspend fun mapPoolLocalToData( poolLocal: UserPoolJoinedLocalNullable, assets: List ): CommonPoolData? { @@ -1072,8 +1067,7 @@ class PoolsRepositoryImpl @Inject constructor( baseReserves = poolLocal.basicPoolLocal.reserveBase, targetReserves = poolLocal.basicPoolLocal.reserveTarget, totalIssuance = poolLocal.basicPoolLocal.totalIssuance, - reserveAccount = poolLocal.basicPoolLocal.reservesAccount, - sbapy = getPoolStrategicBonusAPY(poolLocal.basicPoolLocal.reservesAccount), + reserveAccount = poolLocal.basicPoolLocal.reservesAccount ) val userPoolData = poolLocal.userPoolLocal?.let { userPoolLocal -> diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index ad5819ceab..31f51be5c7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -77,7 +77,6 @@ class PoolsModule { extrinsicService: ExtrinsicService, chainRegistry: ChainRegistry, accountRepository: AccountRepository, - walletRepository: WalletRepository, sorablockexplorer: BlockExplorerManager, poolDao: PoolDao, appDataBase: AppDatabase @@ -86,7 +85,6 @@ class PoolsModule { extrinsicService, chainRegistry, accountRepository, - walletRepository, sorablockexplorer, poolDao, appDataBase diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index eade2bc1e8..43b255076f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -44,10 +44,6 @@ class PoolsInteractorImpl( return poolsRepository.getBasicPools(poolsChainId) } -// override fun subscribePoolsCache(): Flow> { -// return poolsRepository.subscribePools() -// } - override fun subscribePoolsCacheOfAccount(address: String): Flow> { return poolsRepository.subscribePools(address).flowOn(coroutineContext) } @@ -65,7 +61,6 @@ class PoolsInteractorImpl( return soraPoolsAddressFlow.flatMapLatest { address -> poolsRepository.subscribePools(address) } - } override suspend fun getPoolData(baseTokenId: String, targetTokenId: String): Flow { @@ -73,24 +68,12 @@ class PoolsInteractorImpl( return poolsRepository.subscribePool(address, baseTokenId, targetTokenId) } -// override suspend fun getPoolCacheOfCurAccount( -// tokenFromId: String, -// tokenToId: String -// ): CommonUserPoolData? { -// val wallet = accountRepository.getSelectedMetaAccount() -// val chainId = polkaswapInteractor.polkaswapChainId -// val chain = chainRegistry.getChain(chainId) -// val address = wallet.address(chain) -// return poolsRepository.getPoolOfAccount(address, tokenFromId, tokenToId, chainId) -// } - override suspend fun getUserPoolData( chainId: ChainId, address: String, baseTokenId: String, tokenId: ByteArray ): PoolDataDto? { -// return poolsRepository.getPoolOfAccount(address, baseTokenId, tokenId.toHexString(true), polkaswapInteractor.polkaswapChainId) return poolsRepository.getUserPoolData(chainId, address, baseTokenId, tokenId) } @@ -130,9 +113,6 @@ class PoolsInteractorImpl( ) } - override fun getPoolStrategicBonusAPY(reserveAccountOfPool: String): Double? = - poolsRepository.getPoolStrategicBonusAPY(reserveAccountOfPool) - override suspend fun isPairEnabled(baseTokenId: String, targetTokenId: String): Boolean { val dexId = poolsRepository.getPoolBaseTokenDexId(poolsChainId, baseTokenId) return poolsRepository.isPairAvailable( @@ -236,11 +216,13 @@ class PoolsInteractorImpl( override suspend fun syncPools(chainId: ChainId): Unit = withContext(Dispatchers.Default) { val address = accountRepository.getSelectedAccount(chainId).address supervisorScope { - launch { blockExplorerManager.updatePoolsSbApy() } - - launch { poolsRepository.updateAccountPools(chainId, address) } - launch { poolsRepository.updateBasicPools(chainId) } + launch { poolsRepository.updateAccountPools(chainId, address) } + launch { blockExplorerManager.syncSbApy() } } } + + override suspend fun getSbApy(id: String): Double? = withContext(coroutineContext) { + blockExplorerManager.getApy(id) + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 4af04b885f..10a4fe0895 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -80,7 +80,7 @@ class PoolsFlowViewModel @Inject constructor( } val allPoolsScreenState: StateFlow = - allPoolsPresenter.createScreenStateFlow() + allPoolsPresenter.createScreenStateFlow(coroutinesStore.uiScope) val poolListScreenState: StateFlow = poolListPresenter.createScreenStateFlow(coroutinesStore.uiScope) @@ -193,7 +193,7 @@ class PoolsFlowViewModel @Inject constructor( } fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { - val tvl = baseToken?.fiatRate?.times(BigDecimal(2)) + val tvl = baseToken?.fiatRate?.times(BigDecimal(2)) ?.multiply(basic.baseReserves) ?.formatFiat(baseToken.fiatSymbol).orEmpty() @@ -202,7 +202,11 @@ fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { val baseSymbol = basic.baseToken.symbol val targetSymbol = basic.targetToken?.symbol - val userPooledInfo = user?.let { "${it.basePooled.formatCrypto(baseSymbol)} - ${it.targetPooled.formatCrypto(targetSymbol)}" } + val userPooledInfo = user?.let { + "${it.basePooled.formatCrypto(baseSymbol)} - ${ + it.targetPooled.formatCrypto(targetSymbol) + }" + } val text2Color = if (user == null) white50 else user.let { greenText } return BasicPoolListItemState( @@ -212,9 +216,7 @@ fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { text1 = "$baseSymbol-$targetSymbol".uppercase(), text2 = userPooledInfo ?: tvl, text2Color = text2Color, - text3 = basic.sbapy?.let { - "%s%%".format(it.toBigDecimal().formatCrypto()) - }.orEmpty(), + apy = LoadingState.Loading(), text4 = "Earn PSWAP" ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 014f2c2454..8cdf7237cd 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -4,6 +4,8 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState @@ -12,7 +14,9 @@ import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -41,12 +45,15 @@ class AllPoolsPresenter @Inject constructor( chainsRepository.getChain(poolsInteractor.poolsChainId) } - init { - loadPoolData() + private val stateFlow = MutableStateFlow(AllPoolsState()) + + fun createScreenStateFlow(scope: CoroutineScope): StateFlow { + subscribeScreenState(scope) + return stateFlow } - private fun loadPoolData() { - coroutinesStore.ioScope.launch { + private fun subscribeScreenState(scope: CoroutineScope) { + scope.launch { val currentAccount = accountInteractor.selectedMetaAccount() val address = currentAccount.address(chainDeferred.await()) ?: return@launch @@ -56,7 +63,8 @@ class AllPoolsPresenter @Inject constructor( (commonPoolData.map { it.basic.baseToken } + commonPoolData.mapNotNull { it.basic.targetToken }).toSet() val tokensDeferred = - allRequiredChainAssets.filter { it.priceId != null }.map { walletInteractor.getToken(it) } + allRequiredChainAssets.filter { it.priceId != null } + .map { walletInteractor.getToken(it) } val tokensMap = tokensDeferred.associateBy { it.configuration.id } @@ -91,14 +99,50 @@ class AllPoolsPresenter @Inject constructor( isLoading = false ) } - }.launchIn(coroutinesStore.uiScope) + } + .onEach { commonPoolData: List -> + coroutineScope { + commonPoolData.forEach { pool -> + launch { + val baseTokenId = pool.basic.baseToken.currencyId ?: return@launch + val targetTokenId = + pool.basic.targetToken?.currencyId ?: return@launch + val id = StringPair(baseTokenId, targetTokenId) + val sbApy = poolsInteractor.getSbApy(pool.basic.reserveAccount) + + stateFlow.update { prevState -> + val newUserPools = prevState.userPools.map { + if (it.ids == id) { + it.copy(apy = LoadingState.Loaded(sbApy?.let { apy -> + "%s%%".format(apy.toBigDecimal().formatCrypto()) + }.orEmpty())) + } else { + it + } + } + + val newAllPools = prevState.allPools.map { + if (it.ids == id) { + it.copy(apy = LoadingState.Loaded(sbApy?.let { apy -> + "%s%%".format(apy.toBigDecimal().formatCrypto()) + }.orEmpty())) + } else { + it + } + } + + prevState.copy( + userPools = newUserPools, + allPools = newAllPools + ) + } + } + } + } + } + .launchIn(scope) } - } - private val stateFlow = MutableStateFlow(AllPoolsState()) - - fun createScreenStateFlow(): StateFlow { - return stateFlow } override fun onPoolClicked(pair: StringPair) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index dcd6e21b3c..9e17511675 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -33,6 +33,7 @@ import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 +import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.ui_core.resources.Dimens @@ -210,14 +211,14 @@ private fun PreviewAllPoolsScreen() { token2Icon = "DEFAULT_ICON_URI", text1 = "XOR-VAL", text2 = "123.4M", - text3 = "1234.3%", + apy = LoadingState.Loaded("1234.3%"), text4 = "Earn SWAP", ) val items = listOf( itemState, - itemState.copy(text1 = "TEXT1", text2 = "TEXT2", text3 = "TEXT3", text4 = "TEXT4"), - itemState.copy(text1 = "text1", text2 = "text2", text3 = "text3", text4 = "text4"), + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = "TEXT4"), + itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = "text4"), ) AllPoolsScreen( state = AllPoolsState( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index 9d5278c801..c5b9c020ad 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -1,9 +1,9 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -21,28 +21,29 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout -import com.valentinilk.shimmer.shimmer +import coil.compose.SubcomposeAsyncImage import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.component.Shimmer -import jp.co.soramitsu.common.compose.theme.alertYellow +import jp.co.soramitsu.common.compose.component.ShimmerB2 +import jp.co.soramitsu.common.compose.component.getImageRequest import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.customTypography -import jp.co.soramitsu.common.compose.theme.red -import jp.co.soramitsu.common.compose.theme.shimmerColor import jp.co.soramitsu.common.compose.theme.transparent import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white50 -import jp.co.soramitsu.shared_utils.icon.Circle +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.ui_core.component.button.properties.Size -import jp.co.soramitsu.ui_core.component.icons.TokenIcon data class BasicPoolListItemState( val ids: StringPair, @@ -51,7 +52,7 @@ data class BasicPoolListItemState( val text1: String, val text2: String, val text2Color: Color = white50, - val text3: String, + val apy: LoadingState?, val text4: String? = null, ) @@ -75,25 +76,33 @@ fun BasicPoolListItem( .padding(start = 12.dp) ) { val (token1, token2) = createRefs() - TokenIcon( + SubcomposeAsyncImage( modifier = Modifier .constrainAs(token1) { top.linkTo(parent.top, 2.dp) start.linkTo(parent.start) bottom.linkTo(parent.bottom, 11.dp) - }, - uri = state.token1Icon, - size = Size.ExtraSmall, + } + .size(size = 32.dp), + model = getImageRequest(LocalContext.current, state.token1Icon), + contentDescription = null, + loading = { Shimmer(Modifier.size(Size.ExtraSmall)) } ) - TokenIcon( + SubcomposeAsyncImage( modifier = Modifier .constrainAs(token2) { top.linkTo(parent.top, 11.dp) start.linkTo(token1.start, margin = 16.dp) bottom.linkTo(parent.bottom, 2.dp) - }, - uri = state.token2Icon, - size = Size.ExtraSmall, + } + .size(size = 32.dp), + model =getImageRequest(LocalContext.current, state.token2Icon), + contentDescription = null, + loading = { Shimmer(Modifier.size(Size.ExtraSmall)) }, + error = { + it.result.throwable.message?.let { it1 -> Log.d("&&&", it1) } + Icon(painterResource(id = R.drawable.ic_token_default), null, modifier = Modifier.size(size = 32.dp)) + } ) } } @@ -118,17 +127,24 @@ fun BasicPoolListItem( maxLines = 1, overflow = TextOverflow.Ellipsis, ) + when (state.apy) { + is LoadingState.Loaded -> Text( + modifier = Modifier + .wrapContentHeight() + .weight(1f), + color = colorAccentDark, + style = MaterialTheme.customTypography.header6, + text = "%s %s".format( + state.apy.data, + "APY" + ), //stringResource(id = R.string.polkaswap_apy)), + maxLines = 1, + textAlign = TextAlign.End + ) - Text( - modifier = Modifier - .wrapContentHeight() - .weight(1f), - color = colorAccentDark, - style = MaterialTheme.customTypography.header6, - text = "%s %s".format(state.text3, "APY"), //stringResource(id = R.string.polkaswap_apy)), - maxLines = 1, - textAlign = TextAlign.End - ) + is LoadingState.Loading -> ShimmerB2(modifier.width(64.dp)) + null -> Unit + } } Row( @@ -146,7 +162,9 @@ fun BasicPoolListItem( ) Text( - modifier = Modifier.wrapContentHeight().padding(start = 6.dp), + modifier = Modifier + .wrapContentHeight() + .padding(start = 6.dp), color = state.text2Color, style = MaterialTheme.customTypography.body2, text = state.text2, @@ -154,6 +172,7 @@ fun BasicPoolListItem( overflow = TextOverflow.Ellipsis, ) } + } } } @@ -175,22 +194,24 @@ fun BasicPoolShimmerItem( .padding(start = 12.dp) ) { val (token1, token2) = createRefs() - Shimmer(Modifier - .size(Size.ExtraSmall) - .constrainAs(token1) { - top.linkTo(parent.top, 2.dp) - start.linkTo(parent.start) - bottom.linkTo(parent.bottom, 11.dp) - } + Shimmer( + Modifier + .size(Size.ExtraSmall) + .constrainAs(token1) { + top.linkTo(parent.top, 2.dp) + start.linkTo(parent.start) + bottom.linkTo(parent.bottom, 11.dp) + } ) - Shimmer(Modifier - .size(Size.ExtraSmall) - .constrainAs(token2) { - top.linkTo(parent.top, 11.dp) - start.linkTo(token1.start, margin = 16.dp) - bottom.linkTo(parent.bottom, 2.dp) - } + Shimmer( + Modifier + .size(Size.ExtraSmall) + .constrainAs(token2) { + top.linkTo(parent.top, 11.dp) + start.linkTo(token1.start, margin = 16.dp) + bottom.linkTo(parent.bottom, 2.dp) + } ) } } @@ -210,11 +231,13 @@ fun BasicPoolShimmerItem( Shimmer( Modifier .height(12.dp) - .width(70.dp)) + .width(70.dp) + ) Shimmer( Modifier .height(12.dp) - .width(100.dp)) + .width(100.dp) + ) } MarginVertical(margin = 8.dp) Row( @@ -226,11 +249,13 @@ fun BasicPoolShimmerItem( Shimmer( Modifier .height(12.dp) - .width(70.dp)) + .width(70.dp) + ) Shimmer( Modifier .height(12.dp) - .width(90.dp)) + .width(90.dp) + ) } } } @@ -249,7 +274,7 @@ private fun PreviewBasicPoolListItem() { token2Icon = "DEFAULT_ICON_URI", text1 = "XOR-VAL", text2 = "123.4M", - text3 = "1234.3%", + apy = LoadingState.Loaded("1234.3%"), text4 = "Earn SWAP", ) ) @@ -261,7 +286,31 @@ private fun PreviewBasicPoolListItem() { token2Icon = "DEFAULT_ICON_URI", text1 = "text1", text2 = "text2", - text3 = "text3", + apy = LoadingState.Loaded("text3"), + text4 = "text4", + ) + ) + BasicPoolListItem( + modifier = Modifier.background(transparent), + state = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "text1", + text2 = "text2", + apy = LoadingState.Loading(), + text4 = "text4", + ) + ) + BasicPoolListItem( + modifier = Modifier.background(transparent), + state = BasicPoolListItemState( + ids = "0" to "1", + token1Icon = "DEFAULT_ICON_URI", + token2Icon = "DEFAULT_ICON_URI", + text1 = "text1", + text2 = "text2", + apy = null, text4 = "text4", ) ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 3abb7fe233..928e0fa11e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.isZero @@ -35,7 +32,6 @@ import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -58,6 +54,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import kotlin.math.min class LiquidityAddPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -129,23 +129,6 @@ class LiquidityAddPresenter @Inject constructor( } }.mapNotNull { it } -// val assetsFlow2 = walletInteractor.assetsFlow().mapNotNull { -// val firstInPair = it.firstOrNull { -// it.asset.token.configuration.currencyId == ids.first -// && it.asset.token.configuration.chainId == chainId -// } -// val secondInPair = it.firstOrNull { -// it.asset.token.configuration.currencyId == ids.second -// && it.asset.token.configuration.chainId == chainId -// } -// -// println("!!! assetsInPoolFlow ADD result: $firstInPair; $secondInPair") -// if (firstInPair == null || secondInPair == null) { -// return@mapNotNull null -// } else { -// firstInPair.asset to secondInPair.asset -// } -// } assetsFlow }.distinctUntilChanged() @@ -225,7 +208,7 @@ class LiquidityAddPresenter @Inject constructor( isCalculatingAmounts.value == null LiquidityAddState( - apy = pool.basic.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty(), + apy = poolsInteractor.getSbApy(pool.basic.reserveAccount)?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty(), slippage = "$slippage%", feeInfo = feeInfo, buttonEnabled = isButtonEnabled, @@ -253,18 +236,6 @@ class LiquidityAddPresenter @Inject constructor( }.stateIn(coroutineScope, SharingStarted.Lazily, LiquidityAddState()) } -// suspend fun getPoolDataDto(): PoolDataDto? { -// val chain = accountInteractor.getChain(soraMainChainId) -// val address = accountInteractor.selectedMetaAccount().address(chain) -// val assets = assetsInPoolFlow.firstOrNull() -// val baseTokenId = assets?.first?.asset?.token?.configuration?.currencyId -// val tokenToId = assets?.second?.asset?.token?.configuration?.currencyId?.fromHex() -// -// if (address == null || baseTokenId == null || tokenToId == null) return null -// -// return poolsInteractor.getUserPoolData(soraMainChainId, address, baseTokenId, tokenToId) -// } - private suspend fun updateAmounts() { calculateAmount()?.let { targetAmount -> val scaledTargetAmount = when { @@ -437,7 +408,9 @@ class LiquidityAddPresenter @Inject constructor( } val ids = screenArgsFlow.replayCache.lastOrNull()?.ids ?: return@launch - val apy = poolFlow.firstOrNull()?.basic?.sbapy?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty() + val apy = + poolFlow.firstOrNull()?.basic?.reserveAccount?.let { poolsInteractor.getSbApy(it) } + ?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty() internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, apy) }.invokeOnCompletion { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index 85a4d2fcd6..af06259330 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -43,11 +44,6 @@ class PoolDetailsPresenter @Inject constructor( .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - - init { - - } - private val stateFlow = MutableStateFlow(PoolDetailsState()) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { @@ -62,9 +58,11 @@ class PoolDetailsPresenter @Inject constructor( val token = walletInteractor.getToken(pool.basic.baseToken) stateFlow.value = pool.mapToState(token) } -// requestPoolDetails(it.ids)?.let { -// stateFlow.value = it -// } + }.onEach { + val apy = poolsInteractor.getSbApy(it.basic.reserveAccount) + stateFlow.update { prevState -> + prevState.copy(apy = "${apy?.toBigDecimal()?.formatPercent()}%") + } }.launchIn(coroutineScope) } @@ -125,6 +123,6 @@ private fun CommonPoolData.mapToState(token: Token): PoolDetailsState { pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.symbol).orEmpty(), pooledTargetFiat = user?.targetPooled?.applyFiatRate(token.fiatRate)?.formatFiat(token.fiatSymbol).orEmpty(), tvl = tvl?.formatFiat(token.fiatSymbol), - apy = "${basic.sbapy?.toBigDecimal()?.formatPercent()}%" + apy = null//"${basic.sbapy?.toBigDecimal()?.formatPercent()}%" ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 1b3cd19939..626dfc48bf 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import coil.compose.AsyncImage +import coil.compose.SubcomposeAsyncImage import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.GrayButton @@ -33,6 +33,7 @@ import jp.co.soramitsu.common.compose.component.InfoTableItem import jp.co.soramitsu.common.compose.component.InfoTableItemAsset import jp.co.soramitsu.common.compose.component.MarginHorizontal import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.Shimmer import jp.co.soramitsu.common.compose.component.TitleIconValueState import jp.co.soramitsu.common.compose.component.TitleValueViewState import jp.co.soramitsu.common.compose.component.getImageRequest @@ -79,21 +80,23 @@ fun PoolDetailsScreen( MarginVertical(margin = 16.dp) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - AsyncImage( + SubcomposeAsyncImage( model = getImageRequest(LocalContext.current, state.assetBase?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) .offset(x = 14.dp) - .zIndex(1f) + .zIndex(1f), + loading = { Shimmer(Modifier.size(64.dp)) } ) - AsyncImage( + SubcomposeAsyncImage( model = getImageRequest(LocalContext.current, state.assetTarget?.iconUrl.orEmpty()), contentDescription = null, modifier = Modifier .size(64.dp) .offset(x = (-14).dp) - .zIndex(0f) + .zIndex(0f), + loading = { Shimmer(Modifier.size(64.dp)) } ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 8aad5c7992..0323b080e1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -3,15 +3,19 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.poollist import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute +import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -22,8 +26,10 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update import javax.inject.Inject class PoolListPresenter @Inject constructor( @@ -44,9 +50,21 @@ class PoolListPresenter @Inject constructor( } .shareIn(coroutinesStore.uiScope, SharingStarted.Lazily, 1) + @OptIn(ExperimentalCoroutinesApi::class) private val pools = screenArgsFlow.flatMapLatest { screenArgs -> - combine( - poolsInteractor.subscribePoolsCacheCurrentAccount(), + poolsInteractor.subscribePoolsCacheCurrentAccount().map { pools -> + pools.asSequence().filter { + if (screenArgs.isUserPools) { + it.user != null + } else { + true + } + }.toList() + } + } + + private val poolsStates = combine( + pools, enteredAssetQueryFlow ) { pools, query -> coroutineScope { @@ -56,12 +74,6 @@ class PoolListPresenter @Inject constructor( val tokensMap = tokensDeferred.awaitAll().associateBy { it.configuration.id } pools.filter { - if (screenArgs.isUserPools) { - it.user != null - } else { - true - } - }.filter { it.basic.isFilterMatch(query) }.sortedWith { current, next -> val currentTvl = current.basic.getTvl(tokensMap[current.basic.baseToken.id]?.fiatRate) @@ -70,7 +82,6 @@ class PoolListPresenter @Inject constructor( }.mapNotNull { it.toListItemState(tokensMap[it.basic.baseToken.id]) } } } - } private val stateFlow = MutableStateFlow(PoolListState()) @@ -80,13 +91,38 @@ class PoolListPresenter @Inject constructor( } private fun subscribeState(coroutineScope: CoroutineScope) { - pools.onEach { + poolsStates.onEach { stateFlow.value = stateFlow.value.copy(pools = it, isLoading = false) }.launchIn(coroutineScope) enteredAssetQueryFlow.onEach { stateFlow.value = stateFlow.value.copy(searchQuery = it) }.launchIn(coroutineScope) + + pools.onEach { commonPoolData: List -> + val apyMap = coroutineScope.async { + commonPoolData.mapNotNull { pool -> + val baseTokenId = pool.basic.baseToken.currencyId ?: return@mapNotNull null + val targetTokenId = pool.basic.targetToken?.currencyId ?: return@mapNotNull null + val id = StringPair(baseTokenId, targetTokenId) + val sbApy = poolsInteractor.getSbApy(pool.basic.reserveAccount) + id to sbApy + }.toMap() + } + apyMap.join() + stateFlow.update { prevState -> + val newPools = prevState.pools.map { pool -> + apyMap.await()[pool.ids]?.let { sbApy -> + pool.copy(apy = LoadingState.Loaded(sbApy.let { apy -> + "%s%%".format(apy.toBigDecimal().formatCrypto()) + })) + } ?: pool + } + + prevState.copy(pools = newPools) + } + + }.launchIn(coroutineScope) } override fun onPoolClicked(pair: StringPair) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 79068924c1..e2fdb36f73 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -18,6 +18,7 @@ import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.CorneredInput import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.theme.white04 +import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItem import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListItemState @@ -86,14 +87,14 @@ private fun PreviewPoolListScreen() { token2Icon = "DEFAULT_ICON_URI", text1 = "XOR-VAL", text2 = "123.4M", - text3 = "1234.3%", + apy = LoadingState.Loaded("1234.3%"), text4 = "Earn SWAP", ) val items = listOf( itemState, - itemState.copy(text1 = "TEXT1", text2 = "TEXT2", text3 = "TEXT3", text4 = "TEXT4"), - itemState.copy(text1 = "text1", text2 = "text2", text3 = "text3", text4 = "text4"), + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = "TEXT4"), + itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = "text4"), ) PoolListScreen( state = PoolListState( diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt index 049622c2a1..321437397d 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt @@ -9,8 +9,7 @@ data class BasicPoolData( val baseReserves: BigDecimal, val targetReserves: BigDecimal, val totalIssuance: BigDecimal, - val reserveAccount: String, - val sbapy: Double?, + val reserveAccount: String ) { fun getTvl(baseTokenFiatRate: BigDecimal?): BigDecimal? { return baseTokenFiatRate?.times(BigDecimal(2))?.multiply(baseReserves) diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt index 7671ee2523..61e5251d8b 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt +++ b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt @@ -1,36 +1,58 @@ package jp.co.soramitsu.polkaswap.api.sorablockexplorer +import android.util.Log +import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async import javax.inject.Inject import javax.inject.Singleton -import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo -import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.sbapy.SbApyInfo +import kotlin.coroutines.CoroutineContext @Singleton -class BlockExplorerManager @Inject constructor( - private val info: SoraWalletBlockExplorerInfo -) { +class BlockExplorerManager @Inject constructor(private val info: SoraWalletBlockExplorerInfo) { - private val tempApy = mutableListOf() + private val coroutineContext: CoroutineContext = Dispatchers.Default + private val coroutineScope = + CoroutineScope(coroutineContext + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> + println("!!! updateSbApyInternal error% ${throwable.message}") + }) - fun getTempApy(id: String) = tempApy.find { - it.id == id - }?.sbApy?.times(100) + private val apyDeferred = coroutineScope.async { info.getSpApy().associate { it.id to it.sbApy } } - suspend fun updatePoolsSbApy() { - updateSbApyInternal() + suspend fun syncSbApy() { +// apyDeferred.join() + val apy = apyDeferred.await() + Log.d("&&&", "synced sbApy: ${apy.size}") } - private suspend fun updateSbApyInternal() { - runCatching { - val response = info.getSpApy() - println("!!! call blockExplorerManager.updatePoolsSbApy() result size = ${response.size}") - tempApy.clear() - tempApy.addAll(response) - println("!!! call blockExplorerManager.updatePoolsSbApy() result updated") - }.onFailure { - println("!!! updateSbApyInternal error% ${it.message}") - it.printStackTrace() - } + suspend fun getApy(id: String): Double? { + return apyDeferred.await()[id]?.times(100) } +// private val tempApy = mutableListOf() +// +// fun getTempApy(id: String) = tempApy.find { +// it.id == id +// }?.sbApy?.times(100) +// +// suspend fun updatePoolsSbApy() { +// updateSbApyInternal() +// } +// +// private suspend fun updateSbApyInternal() { +// runCatching { +// val response = info.getSpApy() +// println("!!! call blockExplorerManager.updatePoolsSbApy() result size = ${response.size}") +// tempApy.clear() +// tempApy.addAll(response) +// println("!!! call blockExplorerManager.updatePoolsSbApy() result updated") +// }.onFailure { +// println("!!! updateSbApyInternal error% ${it.message}") +// it.printStackTrace() +// } +// } + } \ No newline at end of file From 53d11634dca3b5b4a007342c894c1fffa89eb921 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Sun, 18 Aug 2024 13:35:40 +0500 Subject: [PATCH 52/84] update strings + lokalise --- .../root/presentation/main/MainViewModel.kt | 2 -- .../compose/component/BannerLiquidityPools.kt | 2 +- .../common/compose/component/Notification.kt | 26 +++++++++------- common/src/main/res/values-in/strings.xml | 2 +- common/src/main/res/values-ja/strings.xml | 4 +-- common/src/main/res/values-ru/strings.xml | 6 ++-- common/src/main/res/values-tr/strings.xml | 6 ++-- common/src/main/res/values-vi/strings.xml | 9 +++--- common/src/main/res/values-zh/strings.xml | 6 ++-- common/src/main/res/values/strings.xml | 16 +++------- .../liquiditypools/data/PoolsRepository.kt | 1 - .../domain/interfaces/PoolsInteractor.kt | 2 -- .../impl/data/PoolsRepositoryImpl.kt | 30 ------------------- .../liquiditypools/impl/di/PoolsModule.kt | 8 +++-- .../impl/domain/PoolsInteractorImpl.kt | 9 ++---- .../navigation/InternalPoolsRouterImpl.kt | 9 ++++-- .../impl/presentation/PoolsFlowViewModel.kt | 26 ++++++++-------- .../presentation/allpools/AllPoolsScreen.kt | 15 +++++----- .../allpools/BasicPoolListItem.kt | 20 +++++++------ .../liquidityadd/LiquidityAddPresenter.kt | 8 +---- .../liquidityadd/LiquidityAddScreen.kt | 9 +++--- .../LiquidityAddConfirmPresenter.kt | 19 +++++------- .../LiquidityAddConfirmScreen.kt | 12 ++++---- .../LiquidityRemovePresenter.kt | 28 ++++------------- .../liquidityremove/LiquidityRemoveScreen.kt | 24 +++++++++++++-- .../LiquidityRemoveConfirmPresenter.kt | 17 +++++------ .../LiquidityRemoveConfirmScreen.kt | 5 ++-- .../pooldetails/PoolDetailsScreen.kt | 13 ++++---- .../presentation/poollist/PoolListScreen.kt | 7 +++-- 29 files changed, 144 insertions(+), 197 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt index f82a3c93ca..3c01f15797 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt @@ -40,8 +40,6 @@ class MainViewModel @Inject constructor( .asLiveData() fun navigateToSwapScreen() { - val xorPswap = Pair("b774c386-5cce-454a-a845-1ec0381538ec", "37a999a2-5e90-4448-8b0e-98d06ac8f9d4") -// polkaswapRouter.openAddLiquidity(xorPswap) if (polkaswapInteractor.hasReadDisclaimer) { walletRouter.openSwapTokensScreen( chainId = null, diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt index 610af885c5..da3cb72042 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/BannerLiquidityPools.kt @@ -93,7 +93,7 @@ fun BannerLiquidityPools( maxLines = 2, modifier = Modifier .wrapContentWidth(), - text = stringResource(R.string.banners_liquidity_pools_description), + text = stringResource(R.string.lp_banner_text), style = MaterialTheme.customTypography.paragraphXS.copy(fontSize = 12.sp), color = Color.White ) diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/Notification.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/Notification.kt index 371130064e..e1af8e4526 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/Notification.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/Notification.kt @@ -1,6 +1,7 @@ package jp.co.soramitsu.common.compose.component import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height @@ -23,12 +24,12 @@ data class NotificationState( @DrawableRes val iconRes: Int, val title: String, val value: String, - val buttonText: String, + val buttonText: String? = null, val color: Color ) @Composable -fun Notification(state: NotificationState, onAction: () -> Unit) { +fun Notification(state: NotificationState, onAction: (() -> Unit)? = null) { BackgroundCornered { Row(Modifier.padding(8.dp)) { MarginHorizontal(margin = 8.dp) @@ -44,14 +45,16 @@ fun Notification(state: NotificationState, onAction: () -> Unit) { H6(text = state.title, color = state.color) B1(text = state.value, color = white50) } - TextButtonSmall( - text = state.buttonText, - colors = customButtonColors(state.color), - onClick = onAction, - modifier = Modifier - .height(24.dp) - .align(Alignment.CenterVertically) - ) + onAction?.let { + TextButtonSmall( + text = state.buttonText.orEmpty(), + colors = customButtonColors(state.color), + onClick = onAction, + modifier = Modifier + .height(24.dp) + .align(Alignment.CenterVertically) + ) + } MarginHorizontal(margin = 8.dp) } } @@ -67,7 +70,8 @@ private fun Preview() { stringResource(R.string.staking_unbond_v1_9_0), colorAccent ) - FearlessTheme { + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { Notification(state) {} + Notification(state, onAction = null) } } diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index c39723e1b0..76e9b0decf 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -92,13 +92,13 @@ Tunjukan Seed mentah Jika Anda kehilangan akses ke perangkat ini, dana Anda akan hilang, kecuali Anda membuat cadangan! Pemerintahan - Kumpulan Likuiditas Kumpulan Nominasi Detail terkunci Lindungi diri Anda dari kehilangan akses ke dana Anda Beli XOR Beli atau jual token XOR dengan uang tunai euro Beli token XOR + Kumpulan Likuiditas Peringatan: Dengan mengeklik sambungkan, Anda mengizinkan dapp ini melihat alamat publik Anda. Ini adalah langkah keamanan penting untuk melindungi data Anda dari potensi risiko phishing. Terhubung ke %s Terhubung ke diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 93df6052c3..98cf6164a9 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -471,13 +471,11 @@ 試験ネットワーク 言語 流動性提供のためのファーミング報酬 - 戦略的ボーナス年利 戦略的ボーナス年利 利用可能なプール 流動性を確認 出力は推定です。価格が0.5%以上変動した場合、取引は元に戻されます。 ネットワーク手数料はSORAシステムの成長と安定したパフォーマンスを確保するために使用されます。 - ネットワーク手数料 プールの詳細 プールトークンを削除すると、あなたのポジションが現在のレートで元のトークンに戻されます。プール内のシェアに比例します。受け取る金額には累積された手数料が含まれます。 @@ -488,8 +486,8 @@ スリッページ 流動性を供給 流動性を供給 - %s プール済み ユーザープール + %s プール済み 送信された支払いトランザクション アカウントを追加... トークンまたはネットワークで検索 diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index fdffef1b6c..4ed02933a3 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -408,25 +408,23 @@ Testnet Язык Вознаграждение за предоставление ликвидности - Стратегический бонус APY Стратегический бонус APY Доступные пулы Подтвердите ликвидность Оценка результата. Если цена изменится более чем на 0.5%, ваша транзакция будет отменена Комиссия сети используется для обеспечения роста и стабильной работы системы SORA. - Комиссия сети Детали пула ликвидности Удаление токенов из пула конвертирует вашу позицию обратно в исходные токены по текущему курсу, пропорционально вашей доле в пуле. Начисленные комиссии включены в получаемые вами суммы. ПРИМЕЧАНИЕ Убрать ликвидность Убрать ликвидность - Заработать %@ + Заработать %s Выплата вознаграждений в Проскальзывание Добавить ликвидность Добавить ликвидность - Ваши %s добавлены в пул Пулы пользователя + Ваши %s добавлены в пул Транзакция на выплату отправлена Добавить аккаунт... Поиск по токену diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 716e6a0c92..77ebfbd21e 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -92,13 +92,13 @@ Ham Tohumu Göster Bu cihaza erişimi kaybederseniz, yedekleme yapmadığınız takdirde paranız kaybolacaktır! Yönetim - Likidite Havuzları Adaylık havuzları Kilitli ayrıntılar Fonlarınıza erişiminizi kaybetmekten kendinizi koruyun XOR\'u satın al XOR tokenını Euro nakit ile satın alın veya satın XOR jetonu satın al + Likidite Havuzları Uyarı: Bağlan\'a tıklayarak bu dapp\'in genel adresinizi görüntülemesine izin vermiş olursunuz. Bu, verilerinizi potansiyel kimlik avı risklerinden korumak için önemli bir güvenlik adımıdır Bağlı%s Bağlı @@ -481,13 +481,11 @@ Testnet Dil Likidite sağlanmasına yönelik tarım ödülü - Stratejik Bonus APY Stratejik Bonus APY Mevcut havuzlar Likiditeyi Doğrula Çıktı tahminidir. Fiyatın %0,5\'ten fazla değişmesi durumunda işleminiz geri alınacaktır. Ağ ücreti, SORA sisteminin büyümesini ve istikrarlı performansını sağlamak için kullanılır. - Şebeke ücreti Havuz detayları Havuz tokenlarını kaldırmak, pozisyonunuzu havuzdaki payınızla orantılı olarak mevcut oranda temel tokenlara dönüştürür. Tahakkuk eden ücretler, aldığınız tutarlara dahildir. NOT @@ -498,8 +496,8 @@ Kayma Arz Likiditesinin Arz Likiditesinin - %s Havuzunuz Kullanıcı havuzları + %s Havuzunuz Ödeme işlemi gönderildi. Bir hesap ekle Tokene veya ağa göre ara diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index d1c94b32a7..341cafe832 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -93,13 +93,13 @@ Nếu bạn mất quyền truy cập vào thiết bị này, tiền của bạn sẽ bị mất, trừ khi bạn sao lưu! Bị chặn Quản trị - Pool thanh khoản Nhóm Nomination Chi tiết đã khóa Nếu bạn làm mất thiết bị của mình, bạn\n sẽ mất tiền của bạn mãi mãi Mua XOR Mua hoặc bán tokr XOR với\ntiền Euro Mua token XOR + Pool thanh khoản Cảnh báo: Bằng cách nhấp vào kết nối, bạn cho phép dapp này xem địa chỉ công khai của bạn. Đây là một bước bảo mật quan trọng để bảo vệ dữ liệu của bạn khỏi các nguy cơ lừa đảo tiềm ẩn. Đã kết nối với %s Đã kết nối với @@ -288,6 +288,7 @@ Chọn mạng Đã chọn Chia sẻ + Hiển thị chi tiết Bỏ qua Bỏ qua quá trình @@ -489,15 +490,12 @@ Testnet Ngôn ngữ Phần thưởng canh tác để cung cấp thanh khoản - APY tiền thưởng chiến lược APY tiền thưởng chiến lược Pool có sẵn - Hiển thị chi tiết Đầu tư tiền của bạn vào Pool\nthanh khoản và nhận phần thưởng Xác nhận thanh khoản Kết quả được ước tính. Nếu giá thay đổi hơn 0,5% thì giao dịch của bạn sẽ hoàn trả. Phí mạng được sử dụng để đảm bảo sự tăng trưởng và hiệu suất ổn định của hệ thống SORA. - Phí mạng Chi tiết Pool Việc xóa pool token sẽ chuyển đổi vị trí của bạn trở lại thành token cơ bản ở mức giá hiện tại, tỷ lệ thuận với phần chia sẻ của bạn trong pool. Phí tích lũy được bao gồm trong số tiền bạn nhận được. GHI CHÚ @@ -508,8 +506,8 @@ Trượt giá Nguồnn cung thanh khoản Nguồnn cung thanh khoản - %s của bạn đã đóng góp Pool người dùng + %s của bạn đã đóng góp Giao dịch thanh toán đã được gửi Thêm một tài khoản... Tìm kiếm theo tài sản @@ -554,6 +552,7 @@ Bộ sưu tập Người sáng tạo Chưa có bất kỳ NFT nào. Mua hoặc đúc NFT để xem chúng tại đây. + Không thể tải NFT Sở hữu NFTs sẽ sớm ra mắt Sumimasen! diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml index 3063c13eb0..1a00c8c562 100644 --- a/common/src/main/res/values-zh/strings.xml +++ b/common/src/main/res/values-zh/strings.xml @@ -93,13 +93,13 @@ 如果你失去了对这个设备的访问权限,除非你进行备份,否则你的资金将会丢失! 受阻 治理 - 流动性池 提名池 锁定的详细信息 保护自己避免失去对资金的访问权限。 购买XOR 使用欧元现金购买或出售XOR代币 购买XOR代币 + 流动性池 警告:点击连接按钮,您将允许此dapp查看您的公共地址。这是一个重要的安全步骤,以保护您的数据免受潜在的网络钓鱼风险。 连接到%s 已连接至 @@ -488,13 +488,11 @@ 测试网 语言 提供流动性的农场奖励 - 战略奖励APY 战略奖励APY 可用的流动池 确认流动性 输出是估计的。如果价格变动超过0.5%,您的交易将会回滚。 网络费用用于确保 SORA 系统的增长和稳定性能。 - 网络费用 流动池详情 移除流动性池代币会按照您在池中所占份额的比例,将您的头寸转换回当前汇率下的基础代币。已累积的费用将包含在您收到的金额中。 注意 @@ -505,8 +503,8 @@ 滑点 供应流动性 供应流动性 - 你提供流动性的%s 用户流动池 + 你提供流动性的%s 支付交易已发送 添加一个账户... 按资产搜索 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f9dbecd457..d543efbde8 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -106,7 +106,6 @@ If you loose access to this device, your funds will be lost, unless you back up! Blocked Governance - Liquidity Pools Nomination Pools Locked details Protect yourself from losing access to your funds @@ -115,8 +114,7 @@ Buy XOR Buy or sell XOR token with\nEuro cash Buy XOR token - Invest your funds in Liquidity\npools and receive rewards - Liquidity pools + Liquidity Pools Warning: By clicking connect, you allow this dapp to view your public address. This is an important security step to protect your data from potential phishing risks. Connected to %s Connected to @@ -515,15 +513,13 @@ Testnet Language Farming reward for liquidity provision - Strategic Bonus APY Strategic Bonus APY Available pools - Show details Invest your funds in Liquidity\npools and receive rewards Confirm Liquidity Output is estimated. If the price changes more than 0.5% your transaction will revert. + Your supply to Liquidity pools has been successfully completed Network fee is used to ensure SORA system’s growth and stable performance. - Network fee Pool details Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive. NOTE @@ -534,8 +530,8 @@ Slippage Supply Liquidity Supply Liquidity - Your %s Pooled User pools + Your %s Pooled Payout transaction sent Add an account... Search by asset @@ -635,10 +631,6 @@ + %d other + %d others - Your %s pooled - User pools - Your supply to Liquidity pools has been successfully completed - Available pools Reset to default Swap Swapped @@ -1204,4 +1196,4 @@ What accounts in the wallet do you want to export? Yesterday Your collator - + \ No newline at end of file diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt index 4237dce31d..11f09dc391 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -78,7 +78,6 @@ interface PoolsRepository { suspend fun updateAccountPools(chainId: ChainId, address: String) suspend fun updateBasicPools(chainId: ChainId) -// fun subscribePools(): Flow> fun subscribePools(address: String): Flow> fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index bd8f3730c0..e76d39a0fe 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -12,9 +12,7 @@ interface PoolsInteractor { val poolsChainId: String suspend fun getBasicPools(): List -// fun subscribePoolsCache(): Flow> - // suspend fun getPoolCacheOfCurAccount(tokenFromId: String, tokenToId: String): CommonUserPoolData? fun subscribePoolsCacheOfAccount(address: String): Flow> fun subscribePoolsCacheCurrentAccount(): Flow> suspend fun getPoolData( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index ab86cdf4ca..94fd4b4aaf 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -224,7 +224,6 @@ class PoolsRepositoryImpl @Inject constructor( } override suspend fun getBasicPools(chainId: ChainId): List { - println("!!! getBasicPools() start") val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storage = runtimeOrNull?.metadata ?.module(Modules.POOL_XYK) @@ -239,7 +238,6 @@ class PoolsRepositoryImpl @Inject constructor( val accountId = wallet.accountId(soraChain) val soraAssets = soraChain.assets - println("!!! getBasicPools() soraAssets size: ${soraAssets.size}") soraAssets.forEach { asset -> val currencyId = asset.currencyId @@ -276,7 +274,6 @@ class PoolsRepositoryImpl @Inject constructor( reserveAccount = reserveAccountAddress ) - println("!!! getBasicPools() list.add(BasicPoolData: $element") list.add( element ) @@ -286,8 +283,6 @@ class PoolsRepositoryImpl @Inject constructor( } } - println("!!! getBasicPools() return list.size = ${list.size}") - return list } @@ -859,13 +854,8 @@ class PoolsRepositoryImpl @Inject constructor( } override suspend fun updateAccountPools(chainId: ChainId, address: String) = supervisorScope { - println("!!! call blockExplorerManager.updateAccountPools() chainId = $chainId") - val assets = chainRegistry.getChain(chainId).assets - println("!!! call blockExplorerManager.updateAccountPools() assets = ${assets.size}") - val tokenIds = getUserPoolsTokenIds(chainId, address) - println("!!! call blockExplorerManager.updateAccountPools() tokenIds = ${tokenIds.size}") val poolsDeferred = tokenIds.map { (baseTokenId, tokensId) -> async { val baseToken = assets.firstOrNull { @@ -923,28 +913,12 @@ class PoolsRepositoryImpl @Inject constructor( val pools = poolsDeferred.awaitAll().flatten() db.withTransaction { - println("!!! updateAccountPools: poolDao.clearTable(address)") poolDao.clearTable(address) - - println( - "!!! updateAccountPools: poolDao.insertBasicPools() size = ${ - pools.map { - it.basicPoolLocal - }.size - }" - ) poolDao.insertBasicPools( pools.map { it.basicPoolLocal } ) - println( - "!!! updateAccountPools: poolDao.insertUSERPools() size = ${ - pools.map { - it.userPoolLocal - }.size - }" - ) poolDao.insertUserPools( pools.map { it.userPoolLocal @@ -954,14 +928,11 @@ class PoolsRepositoryImpl @Inject constructor( } override suspend fun updateBasicPools(chainId: ChainId) = coroutineScope { - println("!!! pswapRepo updateBasicPools") val runtimeOrNull = chainRegistry.awaitRuntimeProvider(chainId).get() val storage = runtimeOrNull.metadata .module(Modules.POOL_XYK) .storage("Reserves") -// val list = mutableListOf() - val soraChain = chainRegistry.getChain(chainId) val assets = soraChain.assets @@ -1016,7 +987,6 @@ class PoolsRepositoryImpl @Inject constructor( list.find { it.tokenIdBase == db.tokenIdBase && it.tokenIdTarget == db.tokenIdTarget } == null } poolDao.deleteBasicPools(minus) - println("!!! pswapRepo insertBasicPools(list) size = ${list.size}") poolDao.insertBasicPools(list) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index 31f51be5c7..691385273a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -4,8 +4,10 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.rpc.BulkRetriever +import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.coredb.AppDatabase @@ -24,7 +26,6 @@ import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -43,8 +44,9 @@ class PoolsModule { @Provides @Singleton - fun provideInternalLiquidityPoolsRouter(walletRouter: WalletRouter): InternalPoolsRouter = InternalPoolsRouterImpl( - walletRouter = walletRouter + fun provideInternalLiquidityPoolsRouter(walletRouter: WalletRouter, resourceManager: ResourceManager): InternalPoolsRouter = InternalPoolsRouterImpl( + walletRouter = walletRouter, + resourceManager = resourceManager ) @Provides diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 43b255076f..8dd320da36 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -50,7 +50,6 @@ class PoolsInteractorImpl( private val soraPoolsAddressFlow = flowOf { val meta = accountRepository.getSelectedMetaAccount() - println("!!! accountflow poolsChainId = $poolsChainId") val chain = accountRepository.getChain(poolsChainId) meta.address(chain) }.mapNotNull { it } @@ -75,7 +74,6 @@ class PoolsInteractorImpl( tokenId: ByteArray ): PoolDataDto? { return poolsRepository.getUserPoolData(chainId, address, baseTokenId, tokenId) - } override suspend fun calcAddLiquidityNetworkFee( @@ -132,10 +130,8 @@ class PoolsInteractorImpl( secondAmountMin: BigDecimal, networkFee: BigDecimal ): String { - val address = accountRepository.getSelectedAccount(chainId).address - val status = poolsRepository.observeRemoveLiquidity( - chainId, + chainId, tokenBase, tokenTarget, markerAssetDesired, @@ -143,8 +139,7 @@ class PoolsInteractorImpl( secondAmountMin, ) - - return status?.getOrNull() ?: "" + return status?.getOrNull() ?: "" } override suspend fun observeAddLiquidity( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 082870e192..631cdc714a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -3,6 +3,8 @@ package jp.co.soramitsu.liquiditypools.impl.navigation import java.math.BigDecimal import java.util.Stack import jp.co.soramitsu.androidfoundation.format.StringPair +import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute @@ -15,7 +17,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onEach class InternalPoolsRouterImpl( - private val walletRouter: WalletRouter + private val walletRouter: WalletRouter, + private val resourceManager: ResourceManager ): InternalPoolsRouter { private val routesStack = Stack() @@ -88,10 +91,10 @@ class InternalPoolsRouterImpl( override fun openInfoScreen(itemId: Int) { when (itemId) { PoolsFlowViewModel.ITEM_APY_ID -> { - openInfoScreen("Strategic Bonus APY", "Farming reward for liquidity provision.") + openInfoScreen(resourceManager.getString(res = R.string.lp_apy_title), resourceManager.getString(res = R.string.lp_apy_alert_text)) } PoolsFlowViewModel.ITEM_FEE_ID -> { - openInfoScreen("Network fee", "Network fee is used to ensure SORA system’s growth and stable performance. ") + openInfoScreen(resourceManager.getString(res = R.string.common_network_fee), resourceManager.getString(res = R.string.lp_network_fee_alert_text)) } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 10a4fe0895..0da72e1b44 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -1,7 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.presentation import dagger.hilt.android.lifecycle.HiltViewModel -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.models.TextModel @@ -49,6 +48,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.math.BigDecimal import javax.inject.Inject +import jp.co.soramitsu.common.resources.ResourceManager @HiltViewModel class PoolsFlowViewModel @Inject constructor( @@ -61,9 +61,9 @@ class PoolsFlowViewModel @Inject constructor( liquidityRemoveConfirmPresenter: LiquidityRemoveConfirmPresenter, private val coroutinesStore: CoroutinesStore, private val poolsInteractor: PoolsInteractor, - private val accountInteractor: AccountInteractor, private val internalPoolsRouter: InternalPoolsRouter, private val poolsRouter: LiquidityPoolsRouter, + private val resourceManager: ResourceManager ) : BaseViewModel(), LiquidityAddCallbacks by liquidityAddPresenter, LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter, @@ -138,9 +138,9 @@ class PoolsFlowViewModel @Inject constructor( LiquidityPoolsNavGraphRoute.ListPoolsScreen.routeName -> { val destinationArgs = internalPoolsRouter.destination(LiquidityPoolsNavGraphRoute.ListPoolsScreen::class.java) val titleId = if (destinationArgs?.isUserPools == true) { - R.string.pl_user_pools + R.string.lp_user_pools_title } else { - R.string.pl_available_pools + R.string.lp_available_pools_title } LoadingState.Loaded( @@ -150,27 +150,27 @@ class PoolsFlowViewModel @Inject constructor( LiquidityPoolsNavGraphRoute.PoolDetailsScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("Pools details") + TextModel.ResId(R.string.lp_pool_details_title) ) LiquidityPoolsNavGraphRoute.LiquidityAddScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("Supply liquidity") + TextModel.ResId(R.string.lp_supply_liquidity_screen_title) ) LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("Confirm liquidity") + TextModel.ResId(R.string.lp_confirm_liquidity_screen_title) ) LiquidityPoolsNavGraphRoute.LiquidityRemoveScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("Remove liquidity") + TextModel.ResId(R.string.lp_remove_liquidity_screen_title) ) LiquidityPoolsNavGraphRoute.LiquidityRemoveConfirmScreen.routeName -> LoadingState.Loaded( - TextModel.SimpleString("Remove liquidity") + TextModel.ResId(R.string.lp_remove_liquidity_screen_title) ) else -> LoadingState.Loading() @@ -203,9 +203,9 @@ fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { val baseSymbol = basic.baseToken.symbol val targetSymbol = basic.targetToken?.symbol val userPooledInfo = user?.let { - "${it.basePooled.formatCrypto(baseSymbol)} - ${ - it.targetPooled.formatCrypto(targetSymbol) - }" + val baseCrypto = it.basePooled.formatCrypto(baseSymbol) + val targetCrypto = it.targetPooled.formatCrypto(targetSymbol) + "$baseCrypto - $targetCrypto" } val text2Color = if (user == null) white50 else user.let { greenText } @@ -217,6 +217,6 @@ fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { text2 = userPooledInfo ?: tvl, text2Color = text2Color, apy = LoadingState.Loading(), - text4 = "Earn PSWAP" + text4 = TextModel.ResIdWithArgs(id = R.string.lp_reward_token_text, arrayOf("PSWAP")) ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 9e17511675..c097c2e77d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -30,6 +30,7 @@ import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.Image import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 @@ -87,7 +88,7 @@ fun AllPoolsScreen( .wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_user_pools), + title = stringResource(id = R.string.lp_user_pools_title), onMoreClick = { callback.onMoreClick(true) }.takeIf { state.hasExtraUserPools } ) state.userPools.forEach { pool -> @@ -111,7 +112,7 @@ fun AllPoolsScreen( modifier = Modifier.wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_available_pools), + title = stringResource(id = R.string.lp_available_pools_title), onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) state.allPools.forEach { pool -> @@ -136,7 +137,7 @@ fun ShimmerPoolList(size: Int = 10) { modifier = Modifier.wrapContentHeight() ) { PoolGroupHeader( - title = stringResource(id = R.string.pl_available_pools), + title = stringResource(id = R.string.lp_available_pools_title), onMoreClick = null ) repeat(size) { @@ -174,7 +175,7 @@ private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { .padding(all = Dimens.x1) ) { Text( - text = "MORE", + text = stringResource(id = R.string.common_more).uppercase(), modifier = Modifier .align(Alignment.CenterVertically), maxLines = 1, @@ -212,13 +213,13 @@ private fun PreviewAllPoolsScreen() { text1 = "XOR-VAL", text2 = "123.4M", apy = LoadingState.Loaded("1234.3%"), - text4 = "Earn SWAP", + text4 = TextModel.SimpleString("Earn SWAP"), ) val items = listOf( itemState, - itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = "TEXT4"), - itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = "text4"), + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = TextModel.SimpleString("TEXT4")), + itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = TextModel.SimpleString("text4")), ) AllPoolsScreen( state = AllPoolsState( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index c5b9c020ad..e84710b5e2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -36,6 +37,8 @@ import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.component.Shimmer import jp.co.soramitsu.common.compose.component.ShimmerB2 import jp.co.soramitsu.common.compose.component.getImageRequest +import jp.co.soramitsu.common.compose.models.TextModel +import jp.co.soramitsu.common.compose.models.retrieveString import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.customTypography import jp.co.soramitsu.common.compose.theme.transparent @@ -53,7 +56,7 @@ data class BasicPoolListItemState( val text2: String, val text2Color: Color = white50, val apy: LoadingState?, - val text4: String? = null, + val text4: TextModel? = null, ) @Composable @@ -136,8 +139,8 @@ fun BasicPoolListItem( style = MaterialTheme.customTypography.header6, text = "%s %s".format( state.apy.data, - "APY" - ), //stringResource(id = R.string.polkaswap_apy)), + stringResource(id = R.string.staking_only_apy) + ), maxLines = 1, textAlign = TextAlign.End ) @@ -157,7 +160,7 @@ fun BasicPoolListItem( modifier = Modifier.wrapContentHeight(), color = white50, style = MaterialTheme.customTypography.body2, - text = state.text4.orEmpty(), + text = state.text4?.retrieveString().orEmpty(), maxLines = 1, ) @@ -217,7 +220,6 @@ fun BasicPoolShimmerItem( } Column( modifier = Modifier -// .wrapContentHeight() .weight(1f) .padding(start = 8.dp, end = 12.dp), verticalArrangement = Arrangement.SpaceBetween, @@ -275,7 +277,7 @@ private fun PreviewBasicPoolListItem() { text1 = "XOR-VAL", text2 = "123.4M", apy = LoadingState.Loaded("1234.3%"), - text4 = "Earn SWAP", + text4 = TextModel.SimpleString("Earn SWAP"), ) ) BasicPoolListItem( @@ -287,7 +289,7 @@ private fun PreviewBasicPoolListItem() { text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), - text4 = "text4", + text4 = TextModel.SimpleString("text4"), ) ) BasicPoolListItem( @@ -299,7 +301,7 @@ private fun PreviewBasicPoolListItem() { text1 = "text1", text2 = "text2", apy = LoadingState.Loading(), - text4 = "text4", + text4 = TextModel.SimpleString("text4"), ) ) BasicPoolListItem( @@ -311,7 +313,7 @@ private fun PreviewBasicPoolListItem() { text1 = "text1", text2 = "text2", apy = null, - text4 = "text4", + text4 = TextModel.SimpleString("text4"), ) ) BasicPoolShimmerItem() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 928e0fa11e..8d571fcc96 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -112,7 +112,6 @@ class LiquidityAddPresenter @Inject constructor( val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> val ids = screenArgs.ids val chainId = poolsInteractor.poolsChainId - println("!!! assetsInPoolFlow ADD ids = $ids") val chainAssets = chainsRepository.getChain(chainId).assets val baseAsset = chainAssets.firstOrNull { it.currencyId == ids.first }?.let { walletInteractor.getCurrentAssetOrNull(chainId, it.id) @@ -138,7 +137,6 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(FlowPreview::class) private fun subscribeState(coroutineScope: CoroutineScope) { - println("!!! AddPresenter subscribeState") enteredBaseAmountFlow .onEach { desired = WithDesired.INPUT @@ -310,7 +308,7 @@ class LiquidityAddPresenter @Inject constructor( isPoolPairEnabled ) { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> - val networkFee = getLiquidityNetworkFee( + getLiquidityNetworkFee( tokenBase = baseAsset, tokenTarget = targetAsset, tokenBaseAmount = amountBase, @@ -319,8 +317,6 @@ class LiquidityAddPresenter @Inject constructor( pairPresented = true, slippageTolerance = slippage ) - println("!!!! networkFeeFlow emit $networkFee") - networkFee } @OptIn(ExperimentalCoroutinesApi::class) @@ -376,7 +372,6 @@ class LiquidityAddPresenter @Inject constructor( override fun onAddReviewClick() { setButtonLoading(true) - println("!!! should setButtonLoading(true)") coroutinesStore.uiScope.launch { val chainId = poolsInteractor.poolsChainId @@ -414,7 +409,6 @@ class LiquidityAddPresenter @Inject constructor( internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, apy) }.invokeOnCompletion { - println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { delay(300) setButtonLoading(false) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index c1886e475a..f60ef1532c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import java.math.BigDecimal @@ -135,10 +136,10 @@ fun LiquidityAddScreen( .fillMaxWidth() ) { Column { - InfoTableItem(TitleValueViewState("Slippage", state.slippage)) + InfoTableItem(TitleValueViewState(stringResource(id = R.string.lp_slippage_title), state.slippage)) InfoTableItem( TitleValueViewState( - title = "Strategic bonus APY", + title = stringResource(id = R.string.lp_apy_title), value = state.apy, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) ), @@ -148,14 +149,14 @@ fun LiquidityAddScreen( ) InfoTableItemAsset( TitleIconValueState( - title = "Rewards payout in", + title = stringResource(id = R.string.lp_reward_token_title), iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", value = "PSWAP" ) ) InfoTableItem( TitleValueViewState( - title = "Network fee", + title = stringResource(id = R.string.common_network_fee), value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 380401acac..2508526d23 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -23,6 +23,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -90,10 +91,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( ) } - - init { - - } private val stateFlow = MutableStateFlow(LiquidityAddConfirmState()) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { @@ -134,17 +131,15 @@ class LiquidityAddConfirmPresenter @Inject constructor( isPoolPairEnabled ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> - val networkFee = getLiquidityNetworkFee( + getLiquidityNetworkFee( tokenBase = baseAsset.configuration, tokenTarget = targetAsset.configuration, tokenBaseAmount = screenArgs.amountBase, tokenTargetAmount = screenArgs.amountTarget, pairEnabled = pairEnabled, - pairPresented = true, //pairPresented, + pairPresented = true, slippageTolerance = slippage ) - println("!!!! networkFeeFlow emit $networkFee") - networkFee } @OptIn(ExperimentalCoroutinesApi::class) @@ -225,12 +220,14 @@ class LiquidityAddConfirmPresenter @Inject constructor( // internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) internalPoolsRouter.back() internalPoolsRouter.back() - internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.pl_liquidity_add_complete)) + internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.lp_liquidity_add_complete_text)) } } }.invokeOnCompletion { - println("!!! add confirm invokeOnCompletion") - setButtonLoading(false) + coroutinesStore.uiScope.launch { + delay(300) + setButtonLoading(false) + } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 649283c6fc..62eedbc45a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -143,7 +143,7 @@ fun LiquidityAddConfirmScreen( modifier = Modifier .padding(horizontal = 7.dp) .align(Alignment.CenterHorizontally), - text = "Output is estimated. If the price changes more than 0.5% your transaction will revert.", + text = stringResource(id = R.string.lp_confirm_liquidity_warning_text), textAlign = TextAlign.Center, color = white50 ) @@ -155,10 +155,10 @@ fun LiquidityAddConfirmScreen( ) { Column { MarginVertical(margin = 6.dp) - InfoTableItem(TitleValueViewState("Slippage", state.slippage)) + InfoTableItem(TitleValueViewState(stringResource(id = R.string.lp_slippage_title), state.slippage)) InfoTableItem( TitleValueViewState( - title = "Strategic bonus APY", + title = stringResource(id = R.string.lp_apy_title), value = state.apy, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) ), @@ -166,14 +166,14 @@ fun LiquidityAddConfirmScreen( ) InfoTableItemAsset( TitleIconValueState( - title = "Rewards payout in", + title = stringResource(id = R.string.lp_reward_token_title), iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", value = "PSWAP" ) ) InfoTableItem( TitleValueViewState( - title = "Network fee", + title = stringResource(id = R.string.common_network_fee), value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) @@ -192,7 +192,7 @@ fun LiquidityAddConfirmScreen( .height(48.dp) .padding(horizontal = 16.dp) .fillMaxWidth(), - text = "Confirm", + text = stringResource(id = R.string.common_confirm), enabled = state.buttonEnabled, loading = state.buttonLoading, onClick = callbacks::onConfirmClick diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 52133e75b9..2f7be3fcb0 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -1,6 +1,8 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject import jp.co.soramitsu.androidfoundation.format.isZero import jp.co.soramitsu.common.base.errors.ValidationException import jp.co.soramitsu.common.compose.component.FeeInfoViewState @@ -28,6 +30,7 @@ import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -53,10 +56,6 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject -import kotlin.math.min @OptIn(FlowPreview::class) class LiquidityRemovePresenter @Inject constructor( @@ -66,7 +65,6 @@ class LiquidityRemovePresenter @Inject constructor( private val chainsRepository: ChainsRepository, private val poolsInteractor: PoolsInteractor, private val demeterFarmingInteractor: DemeterFarmingInteractor, - private val accountInteractor: AccountInteractor, private val resourceManager: ResourceManager, private val validateRemoveLiquidityUseCase: ValidateRemoveLiquidityUseCase, ) : LiquidityRemoveCallbacks { @@ -134,7 +132,6 @@ class LiquidityRemovePresenter @Inject constructor( init { coroutinesStore.ioScope.launch { poolDataFlow.map { data -> - println("!!! poolDataFlow data = $data") data.user?.let { CommonUserPoolData( data.basic, @@ -146,7 +143,6 @@ class LiquidityRemovePresenter @Inject constructor( .distinctUntilChanged() .debounce(500) .map { poolDataLocal -> - println("!!! poolDataFlow map poolDataLocal = $poolDataLocal") poolDataReal = poolDataLocal poolInFarming = false @@ -193,12 +189,9 @@ class LiquidityRemovePresenter @Inject constructor( } else { null } - println("!!! poolDataFlow result = $result") result } .collectLatest { poolDataLocal -> - println("!!! poolDataFlow collectLatest poolDataLocal = $poolDataLocal") - poolDataUsable = poolDataLocal amountBase = if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( @@ -290,7 +283,6 @@ class LiquidityRemovePresenter @Inject constructor( }.launchIn(coroutineScope) enteredTargetAmountFlow.onEach { - println("!!! enteredToAmountFlow.onEach = $it") val (_, targetToken) = baseToTargetTokensFlow.first() stateFlow.value = stateFlow.value.copy( @@ -302,7 +294,6 @@ class LiquidityRemovePresenter @Inject constructor( } .debounce(900) .onEach { amount -> - println("!!! enteredToAmountFlow.onEach debounced = $amount") poolDataUsable?.let { amountTarget = if (amount <= it.user.targetPooled) amount else it.user.targetPooled @@ -319,7 +310,6 @@ class LiquidityRemovePresenter @Inject constructor( ) } -// updateAmounts() coroutinesStore.uiScope.launch { updateAmounts() } @@ -359,8 +349,6 @@ class LiquidityRemovePresenter @Inject constructor( private suspend fun updateAmounts() { baseToTargetTokensFlow.firstOrNull()?.let { (tokenBase, tokenTarget) -> if (amountBase.compareTo(stateFlow.value.baseAmountInputViewState.tokenAmount) != 0) { - println("!!! updateAmounts amountFrom to $amountBase") - val scaledAmountBase = when { amountBase.isZero() -> BigDecimal.ZERO else -> amountBase.setScale( @@ -377,7 +365,6 @@ class LiquidityRemovePresenter @Inject constructor( ) } if (amountTarget.compareTo(stateFlow.value.targetAmountInputViewState.tokenAmount) != 0) { - println("!!! updateAmounts amountTo to $amountTarget") val scaledAmountTarget = when { amountTarget.isZero() -> BigDecimal.ZERO else -> amountTarget.setScale( @@ -405,12 +392,10 @@ class LiquidityRemovePresenter @Inject constructor( } private val networkFeeFlow = baseToTargetTokensFlow.map { (baseToken, targetToken) -> - val networkFee = getRemoveLiquidityNetworkFee( + getRemoveLiquidityNetworkFee( tokenBase = baseToken.configuration, tokenTarget = targetToken.configuration, ) - println("!!!! RemoveLiquidity FeeFlow emit $networkFee") - networkFee } @OptIn(ExperimentalCoroutinesApi::class) @@ -516,7 +501,6 @@ class LiquidityRemovePresenter @Inject constructor( internalPoolsRouter.openRemoveLiquidityConfirmScreen(ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired) }.invokeOnCompletion { - println("!!! setButtonLoading(false)") coroutinesStore.uiScope.launch { delay(300) setButtonLoading(false) @@ -525,13 +509,11 @@ class LiquidityRemovePresenter @Inject constructor( } override fun onRemoveBaseAmountChange(amount: BigDecimal) { - println("!!! onRemoveBaseAmountChange(amount = $amount") enteredBaseAmountFlow.value = amount updateButtonState() } override fun onRemoveTargetAmountChange(amount: BigDecimal) { - println("!!! onRemoveToAmountChange(amount = $amount") enteredTargetAmountFlow.value = amount updateButtonState() } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index 0399d59eab..c13dd485b2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import java.math.BigDecimal @@ -28,10 +29,15 @@ import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.compose.component.InfoTableItem import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.Notification +import jp.co.soramitsu.common.compose.component.NotificationState import jp.co.soramitsu.common.compose.component.TitleValueViewState +import jp.co.soramitsu.common.compose.component.WarningInfo +import jp.co.soramitsu.common.compose.component.WarningInfoState import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.grayButtonBackground +import jp.co.soramitsu.common.compose.theme.warningOrange import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R @@ -134,14 +140,14 @@ fun LiquidityRemoveScreen( Column { InfoTableItem( TitleValueViewState( - title = "Transferable Balance", + title = stringResource(id = R.string.assetdetails_balance_transferable), value = state.transferableAmount, additionalValue = state.transferableFiat ) ) InfoTableItem( TitleValueViewState( - title = "Network fee", + title = stringResource(id = R.string.common_network_fee), value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) @@ -154,12 +160,24 @@ fun LiquidityRemoveScreen( MarginVertical(margin = 24.dp) } + Box(modifier = Modifier.padding(horizontal = 16.dp)) { + Notification( + state = NotificationState( + iconRes = R.drawable.ic_warning_filled, + title = stringResource(id = R.string.lp_pool_remove_warning_title).uppercase(), + value = stringResource(id = R.string.lp_pool_remove_warning_text), + color = warningOrange + ) + ) + } + MarginVertical(margin = 16.dp) + AccentButton( modifier = Modifier .height(48.dp) .padding(horizontal = 16.dp) .fillMaxWidth(), - text = "Review", + text = stringResource(id = R.string.common_preview), enabled = state.buttonEnabled, loading = state.buttonLoading, onClick = { runCallback(callbacks::onRemoveReviewClick) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 5de4d0a757..597bf663be 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -22,6 +22,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -122,9 +123,8 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( tokensInPoolFlow, stateSlippage, isPoolPairEnabled - ) - { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> - val networkFee = getLiquidityNetworkFee( + ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> + getLiquidityNetworkFee( tokenBase = baseAsset.configuration, tokenTarget = targetAsset.configuration, tokenBaseAmount = screenArgs.amountBase, @@ -133,8 +133,6 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( pairPresented = true, slippageTolerance = slippage ) - println("!!!! networkFeeFlow emit $networkFee") - networkFee } @OptIn(ExperimentalCoroutinesApi::class) @@ -197,7 +195,6 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( var result = "" - try { result = poolsInteractor.observeRemoveLiquidity( chainId = chainId, @@ -219,12 +216,14 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( // internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) internalPoolsRouter.back() internalPoolsRouter.back() - internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.pl_liquidity_add_complete)) + internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.lp_liquidity_add_complete_text)) } } }.invokeOnCompletion { - println("!!! remove confirm invokeOnCompletion") - setButtonLoading(false) + coroutinesStore.uiScope.launch { + delay(300) + setButtonLoading(false) + } } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt index c8a9e49c9e..9f2afe94cd 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -141,7 +142,7 @@ fun LiquidityRemoveConfirmScreen( InfoTableItem( TitleValueViewState( - title = "Network fee", + title = stringResource(id = R.string.common_network_fee), value = state.feeInfo.feeAmount, additionalValue = state.feeInfo.feeAmountFiat, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) @@ -159,7 +160,7 @@ fun LiquidityRemoveConfirmScreen( .height(48.dp) .padding(horizontal = 16.dp) .fillMaxWidth(), - text = "Confirm", + text = stringResource(id = R.string.common_confirm), enabled = state.buttonEnabled, loading = state.buttonLoading, onClick = callbacks::onRemoveConfirmClick diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 626dfc48bf..465af9c2d2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -143,7 +143,7 @@ fun PoolDetailsScreen( InfoTableItem(TitleValueViewState("TVL", state.tvl)) InfoTableItem( TitleValueViewState( - title = "Strategic bonus APY", + title = stringResource(id = R.string.lp_apy_title), value = state.apy, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) ), @@ -151,7 +151,7 @@ fun PoolDetailsScreen( ) InfoTableItemAsset( TitleIconValueState( - title = "Rewards payout in", + title = stringResource(id = R.string.lp_reward_token_title), iconUrl = "https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/icons/tokens/coloured/PSWAP.svg", value = "PSWAP" ) @@ -159,7 +159,7 @@ fun PoolDetailsScreen( if (state.pooledBaseAmount.isNotEmpty()) { InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetBase?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.lp_your_pooled_format, state.assetBase?.symbol?.uppercase().orEmpty()), value = state.pooledBaseAmount, additionalValue = state.pooledBaseFiat ) @@ -168,12 +168,11 @@ fun PoolDetailsScreen( if (state.pooledTargetAmount.isNotEmpty()) { InfoTableItem( TitleValueViewState( - title = stringResource(id = R.string.pl_your_pooled_format, state.assetTarget?.symbol?.uppercase().orEmpty()), + title = stringResource(id = R.string.lp_your_pooled_format, state.assetTarget?.symbol?.uppercase().orEmpty()), value = state.pooledTargetAmount, additionalValue = state.pooledTargetFiat ) ) - } } } @@ -185,7 +184,7 @@ fun PoolDetailsScreen( .height(48.dp) .padding(horizontal = 16.dp) .fillMaxWidth(), - text = "Supply liquidity", + text = stringResource(id = R.string.lp_supply_button_title), onClick = callbacks::onSupplyLiquidityClick ) MarginVertical(margin = 8.dp) @@ -196,7 +195,7 @@ fun PoolDetailsScreen( .height(48.dp) .padding(horizontal = 16.dp) .fillMaxWidth(), - text = "Remove liquidity", + text = stringResource(id = R.string.lp_remove_button_title), onClick = callbacks::onRemoveLiquidityClick ) MarginVertical(margin = 8.dp) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index e2fdb36f73..40a6e60d91 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.CorneredInput import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.theme.white04 import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_wallet_impl.R @@ -88,13 +89,13 @@ private fun PreviewPoolListScreen() { text1 = "XOR-VAL", text2 = "123.4M", apy = LoadingState.Loaded("1234.3%"), - text4 = "Earn SWAP", + text4 = TextModel.SimpleString("Earn SWAP"), ) val items = listOf( itemState, - itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = "TEXT4"), - itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = "text4"), + itemState.copy(text1 = "TEXT1", text2 = "TEXT2", apy = LoadingState.Loaded("TEXT3"), text4 = TextModel.SimpleString("TEXT4")), + itemState.copy(text1 = "text1", text2 = "text2", apy = LoadingState.Loaded("text3"), text4 = TextModel.SimpleString("text4")), ) PoolListScreen( state = PoolListState( From 733c6245e4f3ce0615d7dc01664ef60c1ce50530 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Sun, 18 Aug 2024 15:18:29 +0500 Subject: [PATCH 53/84] clean; refactor --- .../blockexplorer/BlockExplorerManager.kt | 32 +++ .../liquiditypools}/data/LiquidityData.kt | 2 +- .../liquiditypools}/data/PoolDataDto.kt | 2 +- .../liquiditypools/data/PoolsRepository.kt | 5 +- .../domain/interfaces/PoolsInteractor.kt | 6 +- .../domain/model}/BasicPoolData.kt | 2 +- .../domain/model}/UserPoolData.kt | 5 +- .../navigation/LiquidityPoolsNavGraphRoute.kt | 3 +- .../liquiditypools/navigation/NavAction.kt | 2 +- feature-liquiditypools-impl/build.gradle.kts | 32 +-- .../impl/data/PoolsRepositoryImpl.kt | 224 ++++-------------- .../impl/data/network/Extrinsic.kt | 124 ++++++++++ .../liquiditypools/impl/di/PoolsModule.kt | 7 +- .../impl/domain/PoolsInteractorImpl.kt | 46 +--- .../impl/presentation/CoroutinesStore.kt | 14 +- .../impl/presentation/PoolsFlowFragment.kt | 2 - .../impl/presentation/PoolsFlowViewModel.kt | 6 +- .../allpools/AllPoolsPresenter.kt | 8 +- .../liquidityadd/LiquidityAddPresenter.kt | 2 +- .../LiquidityRemovePresenter.kt | 4 +- .../liquidityremove/LiquidityRemoveScreen.kt | 2 - .../pooldetails/PoolDetailsPresenter.kt | 8 +- .../poollist/PoolListPresenter.kt | 13 +- .../impl/util/PolkaswapFormulas.kt | 2 +- .../sorablockexplorer/BlockExplorerManager.kt | 58 ----- .../impl/data/network/blockchain/Extrinsic.kt | 123 +--------- .../impl/di/PolkaswapFeatureBindModule.kt | 10 - .../balance/list/BalanceListViewModel.kt | 4 +- 28 files changed, 255 insertions(+), 493 deletions(-) create mode 100644 feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt rename {feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api => feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools}/data/LiquidityData.kt (87%) rename {feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api => feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools}/data/PoolDataDto.kt (86%) rename {feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models => feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model}/BasicPoolData.kt (89%) rename {feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models => feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model}/UserPoolData.kt (73%) create mode 100644 feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt rename {feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap => feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools}/impl/util/PolkaswapFormulas.kt (98%) delete mode 100644 feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt new file mode 100644 index 0000000000..ff811114f8 --- /dev/null +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.liquiditypools.blockexplorer + +import android.util.Log +import javax.inject.Inject +import javax.inject.Singleton +import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async + +@Singleton +class BlockExplorerManager @Inject constructor(private val info: SoraWalletBlockExplorerInfo) { + + private val coroutineContext: CoroutineContext = Dispatchers.Default + private val coroutineScope = + CoroutineScope(coroutineContext + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> + Log.e("BlockExplorerManager", throwable.message.orEmpty()) + }) + + private val apyDeferred = coroutineScope.async { info.getSpApy().associate { it.id to it.sbApy } } + + suspend fun syncSbApy() { + apyDeferred.await() + } + + suspend fun getApy(id: String): Double? { + return apyDeferred.await()[id]?.times(100) + } +} \ No newline at end of file diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/LiquidityData.kt similarity index 87% rename from feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt rename to feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/LiquidityData.kt index f249288bbc..eb58a25a90 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/LiquidityData.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/LiquidityData.kt @@ -1,4 +1,4 @@ -package jp.co.soramitsu.polkaswap.api.data +package jp.co.soramitsu.liquiditypools.data import java.math.BigDecimal diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolDataDto.kt similarity index 86% rename from feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt rename to feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolDataDto.kt index 875bd84ad5..72d2b7c566 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/data/PoolDataDto.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolDataDto.kt @@ -1,4 +1,4 @@ -package jp.co.soramitsu.polkaswap.api.data +package jp.co.soramitsu.liquiditypools.data import java.math.BigInteger diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt index 11f09dc391..f3fbc2e529 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -1,9 +1,8 @@ package jp.co.soramitsu.liquiditypools.data import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.liquiditypools.domain.model.BasicPoolData +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import kotlinx.coroutines.flow.Flow diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index e76d39a0fe..65eae9290e 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -1,9 +1,9 @@ package jp.co.soramitsu.liquiditypools.domain.interfaces import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData +import jp.co.soramitsu.liquiditypools.data.PoolDataDto +import jp.co.soramitsu.liquiditypools.domain.model.BasicPoolData +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigDecimal diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt similarity index 89% rename from feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt rename to feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt index 321437397d..16afb0cea6 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/BasicPoolData.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt @@ -1,4 +1,4 @@ -package jp.co.soramitsu.polkaswap.api.domain.models +package jp.co.soramitsu.liquiditypools.domain.model import jp.co.soramitsu.core.models.Asset import java.math.BigDecimal diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt similarity index 73% rename from feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt rename to feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt index 6185b8a76f..3047f5670c 100644 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/domain/models/UserPoolData.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt @@ -1,4 +1,4 @@ -package jp.co.soramitsu.polkaswap.api.domain.models +package jp.co.soramitsu.liquiditypools.domain.model import java.math.BigDecimal @@ -13,7 +13,6 @@ data class CommonPoolData( ) data class UserPoolData( -// val address: String?, val basePooled: BigDecimal, val targetPooled: BigDecimal, val poolShare: Double, @@ -22,11 +21,9 @@ data class UserPoolData( fun BasicPoolData.isFilterMatch(filter: String): Boolean { val t1 = -// targetToken?.token?.configuration?.name?.lowercase()?.contains(filter.lowercase()) == true || targetToken?.symbol?.lowercase()?.contains(filter.lowercase()) == true || targetToken?.currencyId?.lowercase()?.contains(filter.lowercase()) == true val t2 = -// baseToken.token.configuration.name?.lowercase()?.contains(filter.lowercase()) == true || baseToken.symbol.lowercase().contains(filter.lowercase()) || baseToken.currencyId?.lowercase()?.contains(filter.lowercase()) == true return t1 || t2 diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 58994f5b96..4cf6529b09 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -2,13 +2,12 @@ package jp.co.soramitsu.liquiditypools.navigation import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.StringPair -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId sealed interface LiquidityPoolsNavGraphRoute { val routeName: String - object Loading : LiquidityPoolsNavGraphRoute { + data object Loading : LiquidityPoolsNavGraphRoute { override val routeName: String = "Loading" } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt index 5da08397a6..f261fbf805 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.liquiditypools.navigation sealed interface NavAction { - object BackPressed : NavAction + data object BackPressed : NavAction class ShowError( val errorTitle: String?, diff --git a/feature-liquiditypools-impl/build.gradle.kts b/feature-liquiditypools-impl/build.gradle.kts index 7f88ab50c2..6cc5d60fa3 100644 --- a/feature-liquiditypools-impl/build.gradle.kts +++ b/feature-liquiditypools-impl/build.gradle.kts @@ -1,5 +1,3 @@ -import groovy.lang.Closure - plugins { id("com.android.library") id("dagger.hilt.android.plugin") @@ -40,39 +38,17 @@ dependencies { implementation(projects.common) implementation(projects.runtime) implementation(projects.featurePolkaswapApi) - implementation(projects.featurePolkaswapImpl) + implementation(projects.featureLiquiditypoolsApi) + implementation(projects.featureAccountApi) + implementation(projects.featureWalletApi) + implementation(projects.featureWalletImpl) -// implementation(project(":feature-wallet-api")) -// implementation(libs.hilt.android) kapt(libs.hilt.compiler) implementation(libs.bundles.compose) implementation(libs.fragmentKtx) implementation(libs.material) -// implementation(libs.sharedFeaturesCoreDep) { -// exclude(module = "android-foundation") -// } -// implementation(libs.retrofit) -// implementation(libs.gson) -// implementation(libs.web3jDep) { -// exclude(group = "org.java-websocket", module = "Java-WebSocket") -// } -// -// implementation(libs.converter.gson) -// implementation(libs.converter.scalars) -// implementation(libs.navigation.compose) implementation(libs.sora.ui) implementation(libs.room.ktx) -// -// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.7.3") -// - implementation(projects.featureLiquiditypoolsApi) -// implementation(projects.featureNftApi) - implementation(projects.featureAccountApi) - implementation(projects.featureWalletApi) - implementation(projects.featureWalletImpl) -//// implementation(projects.coreDb) -// implementation(projects.coreApi) -// implementation(kotlin("script-runtime")) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index 94fd4b4aaf..e28081b66c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -1,6 +1,9 @@ package jp.co.soramitsu.liquiditypools.impl.data import androidx.room.withTransaction +import java.math.BigDecimal +import java.math.BigInteger +import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.androidfoundation.format.addHexPrefix @@ -17,18 +20,18 @@ import jp.co.soramitsu.coredb.model.BasicPoolLocal import jp.co.soramitsu.coredb.model.UserPoolJoinedLocal import jp.co.soramitsu.coredb.model.UserPoolJoinedLocalNullable import jp.co.soramitsu.coredb.model.UserPoolLocal +import jp.co.soramitsu.liquiditypools.blockexplorer.BlockExplorerManager +import jp.co.soramitsu.liquiditypools.data.PoolDataDto import jp.co.soramitsu.liquiditypools.data.PoolsRepository -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.UserPoolData -import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.depositLiquidity -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.initializePool -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.liquidityAdd -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.register -import jp.co.soramitsu.polkaswap.impl.data.network.blockchain.removeLiquidity -import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas +import jp.co.soramitsu.liquiditypools.domain.model.BasicPoolData +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData +import jp.co.soramitsu.liquiditypools.domain.model.UserPoolData +import jp.co.soramitsu.liquiditypools.impl.data.network.depositLiquidity +import jp.co.soramitsu.liquiditypools.impl.data.network.initializePool +import jp.co.soramitsu.liquiditypools.impl.data.network.liquidityAdd +import jp.co.soramitsu.liquiditypools.impl.data.network.register +import jp.co.soramitsu.liquiditypools.impl.data.network.removeLiquidity +import jp.co.soramitsu.liquiditypools.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId @@ -70,9 +73,6 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.supervisorScope -import java.math.BigDecimal -import java.math.BigInteger -import javax.inject.Inject class PoolsRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, @@ -300,7 +300,7 @@ class PoolsRepositoryImpl @Inject constructor( reservesAccount, address.toAccountId() ) - } ?: error("!!! subscribeAccountPoolProviders poolProvidersKey is null") + } ?: error("subscribeAccountPoolProviders poolProvidersKey is null") val poolProvidersFlow = chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( SubscribeStorageRequest(poolProvidersKey), @@ -525,132 +525,16 @@ class PoolsRepositoryImpl @Inject constructor( val storageKey = chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) ?: return null - return try { - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = scale(ReservesResponse), - ) - .result - ?.let { storage -> - storage[storage.schema.first] to storage[storage.schema.second] - } - } catch (e: Exception) { - println("!!! getPairWithXorReserves error = ${e.message}") - - e.printStackTrace() - throw e - } + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = scale(ReservesResponse), + ) + .result + ?.let { storage -> + storage[storage.schema.first] to storage[storage.schema.second] + } } -// override suspend fun getPoolOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): CommonUserPoolData? { -// return getPoolsOfAccount(address, tokenFromId, tokenToId, chainId).firstOrNull { -// it.user.address == address -// } -// } - -// suspend fun getPoolsOfAccount(address: String?, tokenFromId: String, tokenToId: String, chainId: String): List { -//// val runtimeOrNull = chainRegistry.getRuntimeOrNull(soraMainChainId) -//// val socketService = chainRegistry.getConnection(chainId).socketService -//// socketService.executeAsyncCatching() -// val tokensPair: List>>? = address?.let { -// getUserPoolsTokenIds(it) -// } -// -// val pools = mutableListOf() -// -// tokensPair?.forEach { (baseTokenId, tokensId) -> -// tokensId.mapNotNull { tokenId -> -// getUserPoolData(address, baseTokenId, tokenId) -// }.forEach pool@{ poolDataDto -> -// val metaId = accountRepository.getSelectedLightMetaAccount().id -// val assets = walletRepository.getAssets(metaId) -// val token = assets.firstOrNull { -// it.token.configuration.currencyId == poolDataDto.assetId -// } ?: return@pool -// val baseToken = assets.firstOrNull { -// it.token.configuration.currencyId == baseTokenId -// } ?: return@pool -// val xorPrecision = baseToken?.token?.configuration?.precision ?: 0 -// val tokenPrecision = token?.token?.configuration?.precision ?: 0 -// -// val apy = getPoolStrategicBonusAPY(poolDataDto.reservesAccount) -// -// val basePooled = PolkaswapFormulas.calculatePooledValue( -// mapBalance( -// poolDataDto.reservesFirst, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// baseToken?.token?.configuration?.precision, -// ) -// val secondPooled = PolkaswapFormulas.calculatePooledValue( -// mapBalance( -// poolDataDto.reservesSecond, -// tokenPrecision -// ), -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// token?.token?.configuration?.precision, -// ) -// val share = PolkaswapFormulas.calculateShareOfPoolFromAmount( -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// ) -// val userPoolData = CommonUserPoolData( -// basic = BasicPoolData( -// baseToken = baseToken, -// targetToken = token, -// baseReserves = mapBalance( -// poolDataDto.reservesFirst, -// xorPrecision -// ), -// targetReserves = mapBalance( -// poolDataDto.reservesSecond, -// tokenPrecision -// ), -// totalIssuance = mapBalance( -// poolDataDto.totalIssuance, -// xorPrecision -// ), -// reserveAccount = poolDataDto.reservesAccount, -// sbapy = apy, -// ), -// user = UserPoolData( -//// address = address, -// basePooled = basePooled, -// targetPooled = secondPooled, -// share, -// mapBalance( -// poolDataDto.poolProvidersBalance, -// xorPrecision -// ), -// ), -// ) -// pools.add(userPoolData) -// } -// } -// return pools -// } - fun RuntimeSnapshot.reservesKey(baseTokenId: String, tokenId: ByteArray): String = this.metadata.module(Modules.POOL_XYK) .storage("Reserves") @@ -728,47 +612,39 @@ class PoolsRepositoryImpl @Inject constructor( private suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) - return runCatching { - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = StateKeys(listOfNotNull(accountPoolsKey)), - mapper = pojoList().nonNull() - ) - }.onFailure { - println("!!! getUserPoolsTokenIdsKeys error: ${it.message}") - it.printStackTrace() - } - .getOrThrow() + return chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = StateKeys(listOfNotNull(accountPoolsKey)), + mapper = pojoList().nonNull() + ) } private suspend fun getUserPoolsTokenIds( chainId: ChainId, address: String ): List>> { - return runCatching { - val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) - storageKeys.map { storageKey -> - chainRegistry.awaitConnection(chainId).socketService.executeAsync( - request = GetStorageRequest(listOf(storageKey)), - mapper = pojo().nonNull(), - ) - .let { storage -> - val storageType = - chainRegistry.getRuntimeOrNull(chainId)?.metadata?.module(Modules.POOL_XYK) - ?.storage("AccountPools")?.type?.value!! - val storageRawData = - storageType.fromHex(chainRegistry.getRuntimeOrNull(chainId)!!, storage) - val tokens: List = if (storageRawData is List<*>) { - storageRawData.filterIsInstance() - .mapNotNull { struct -> - struct.getTokenId() - } - } else { - emptyList() - } - storageKey.assetIdFromKey() to tokens + val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) + return storageKeys.map { storageKey -> + chainRegistry.awaitConnection(chainId).socketService.executeAsync( + request = GetStorageRequest(listOf(storageKey)), + mapper = pojo().nonNull(), + ) + .let { storage -> + val storageType = + chainRegistry.getRuntimeOrNull(chainId)?.metadata?.module(Modules.POOL_XYK) + ?.storage("AccountPools")?.type?.value!! + val storageRawData = + storageType.fromHex(chainRegistry.getRuntimeOrNull(chainId)!!, storage) + val tokens: List = if (storageRawData is List<*>) { + storageRawData.filterIsInstance() + .mapNotNull { struct -> + struct.getTokenId() + } + } else { + emptyList() } - } - }.getOrThrow() + storageKey.assetIdFromKey() to tokens + } + } } override suspend fun observeRemoveLiquidity( @@ -1020,7 +896,7 @@ class PoolsRepositoryImpl @Inject constructor( .storage("AccountPools") .storageKey(this, address.toAccountId()) - private suspend fun mapPoolLocalToData( + private fun mapPoolLocalToData( poolLocal: UserPoolJoinedLocalNullable, assets: List ): CommonPoolData? { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt new file mode 100644 index 0000000000..cfca067e2e --- /dev/null +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt @@ -0,0 +1,124 @@ +package jp.co.soramitsu.liquiditypools.impl.data.network + +import java.math.BigInteger +import jp.co.soramitsu.common.utils.Modules +import jp.co.soramitsu.shared_utils.extensions.fromHex +import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct +import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder + +fun ExtrinsicBuilder.register( + dexId: Int, + baseAssetId: String, + targetAssetId: String +) = this.call( + "TradingPair", + "register", + mapOf( + "dex_id" to dexId.toBigInteger(), + "base_asset_id" to baseAssetId.mapCodeToken(), + "target_asset_id" to targetAssetId.mapCodeToken(), + ) +) + +fun ExtrinsicBuilder.initializePool( + dexId: Int, + baseAssetId: String, + targetAssetId: String +) = this.call( + Modules.POOL_XYK, + "initialize_pool", + mapOf( + "dex_id" to dexId.toBigInteger(), + "asset_a" to baseAssetId.mapCodeToken(), + "asset_b" to targetAssetId.mapCodeToken(), + ) +) + +fun ExtrinsicBuilder.depositLiquidity( + dexId: Int, + baseAssetId: String, + targetAssetId: String, + baseAssetAmount: BigInteger, + targetAssetAmount: BigInteger, + amountFromMin: BigInteger, + amountToMin: BigInteger +) = this.call( + Modules.POOL_XYK, + "deposit_liquidity", + mapOf( + "dex_id" to dexId.toBigInteger(), + "input_asset_a" to baseAssetId.mapCodeToken(), + "input_asset_b" to targetAssetId.mapCodeToken(), + "input_a_desired" to baseAssetAmount, + "input_b_desired" to targetAssetAmount, + "input_a_min" to amountFromMin, + "input_b_min" to amountToMin + ) +) + +fun ExtrinsicBuilder.removeLiquidity( + dexId: Int, + outputAssetIdA: String, + outputAssetIdB: String, + markerAssetDesired: BigInteger, + outputAMin: BigInteger, + outputBMin: BigInteger +) = + this.call( + Modules.POOL_XYK, + "withdraw_liquidity", + mapOf( + "dex_id" to dexId.toBigInteger(), + "output_asset_a" to outputAssetIdA.mapCodeToken(), + "output_asset_b" to outputAssetIdB.mapCodeToken(), + "marker_asset_desired" to markerAssetDesired, + "output_a_min" to outputAMin, + "output_b_min" to outputBMin + ) + ) + +fun ExtrinsicBuilder.liquidityAdd( + dexId: Int, + baseTokenId: String?, + targetTokenId: String?, + pairPresented: Boolean, + pairEnabled: Boolean, + tokenBaseAmount: BigInteger, + tokenTargetAmount: BigInteger, + amountBaseMin: BigInteger, + amountTargetMin: BigInteger +) { + if (baseTokenId != null && targetTokenId != null) { + if (!pairPresented) { + if (!pairEnabled) { + register( + dexId = dexId, + baseTokenId, + targetTokenId + ) + } + initializePool( + dexId = dexId, + baseTokenId, + targetTokenId + ) + } + + depositLiquidity( + dexId = dexId, + baseTokenId, + targetTokenId, + tokenBaseAmount, + tokenTargetAmount, + amountBaseMin, + amountTargetMin, + ) + } +} + +fun String.mapCodeToken() = Struct.Instance( + mapOf("code" to this.mapAssetId()) +) + +fun String.mapAssetId() = this.fromHex().mapAssetId() +fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index 691385273a..0300c5b244 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -9,9 +9,9 @@ import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.core.extrinsic.ExtrinsicService -import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.coredb.AppDatabase import jp.co.soramitsu.coredb.dao.PoolDao +import jp.co.soramitsu.liquiditypools.blockexplorer.BlockExplorerManager import jp.co.soramitsu.liquiditypools.data.DemeterFarmingRepository import jp.co.soramitsu.liquiditypools.data.PoolsRepository import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor @@ -22,7 +22,6 @@ import jp.co.soramitsu.liquiditypools.impl.domain.DemeterFarmingInteractorImpl import jp.co.soramitsu.liquiditypools.impl.domain.PoolsInteractorImpl import jp.co.soramitsu.liquiditypools.impl.navigation.InternalPoolsRouterImpl import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter -import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter @@ -36,11 +35,9 @@ class PoolsModule { fun providesPoolInteractor( poolsRepository: PoolsRepository, accountRepository: AccountRepository, - chainRegistry: ChainRegistry, - keypairProvider: KeypairProvider, blockExplorerManager: BlockExplorerManager ): PoolsInteractor = - PoolsInteractorImpl(poolsRepository, accountRepository, chainRegistry, keypairProvider, blockExplorerManager) + PoolsInteractorImpl(poolsRepository, accountRepository, blockExplorerManager) @Provides @Singleton diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 8dd320da36..97af04dea3 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -1,21 +1,21 @@ package jp.co.soramitsu.liquiditypools.impl.domain +import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.data.secrets.v1.Keypair import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets import jp.co.soramitsu.common.utils.flowOf -import jp.co.soramitsu.core.extrinsic.keypair_provider.KeypairProvider import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.liquiditypools.blockexplorer.BlockExplorerManager +import jp.co.soramitsu.liquiditypools.data.PoolDataDto import jp.co.soramitsu.liquiditypools.data.PoolsRepository import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor -import jp.co.soramitsu.polkaswap.api.data.PoolDataDto -import jp.co.soramitsu.polkaswap.api.domain.models.BasicPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager -import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.liquiditypools.domain.model.BasicPoolData +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -26,14 +26,10 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext -import java.math.BigDecimal -import kotlin.coroutines.CoroutineContext class PoolsInteractorImpl( private val poolsRepository: PoolsRepository, private val accountRepository: AccountRepository, - private val chainRegistry: ChainRegistry, - private val keypairProvider: KeypairProvider, private val blockExplorerManager: BlockExplorerManager, private val coroutineContext: CoroutineContext = Dispatchers.Default ) : PoolsInteractor { @@ -155,18 +151,6 @@ class PoolsInteractorImpl( val metaAccount = accountRepository.getSelectedMetaAccount() val address = accountRepository.getSelectedAccount(chainId).address - val networkFee = calcAddLiquidityNetworkFee( - chainId, - address, - tokenBase, - tokenTarget, - amountBase, - amountTarget, - enabled, - presented, - slippageTolerance - ) - val secrets = accountRepository.getMetaAccountSecrets(metaAccount.id)?.get(MetaAccountSecrets.SubstrateKeypair) requireNotNull(secrets) val private = secrets[KeyPairSchema.PrivateKey] @@ -187,24 +171,6 @@ class PoolsInteractorImpl( slippageTolerance ) - - - if (status != null) { -// transactionHistoryRepository.saveTransaction( -// transactionBuilder.buildLiquidity( -// txHash = status.txHash, -// blockHash = status.blockHash, -// fee = networkFee, -// status = TransactionStatus.PENDING, -// date = Date().time, -// token1 = tokenFrom, -// token2 = tokenTo, -// amount1 = amountFrom, -// amount2 = amountTo, -// type = TransactionLiquidityType.ADD, -// ) -// ) - } return status?.getOrNull() ?: "" } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt index 46747dc6be..fc9a1dcdf9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt @@ -9,12 +9,15 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import javax.inject.Inject import javax.inject.Singleton +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import kotlin.coroutines.CoroutineContext import kotlin.properties.Delegates @Singleton class CoroutinesStore @Inject constructor( private val resourceManager: ResourceManager, + private val internalPoolsRouter: InternalPoolsRouter ) { val uiScope: CoroutineScope by Delegates.cachedOrNew(isCorrupted = ::isScopeCanceled) { @@ -29,13 +32,10 @@ class CoroutinesStore @Inject constructor( return scope.coroutineContext[Job]?.isActive != true } - @Suppress("UnusedParameter") private fun handleException(coroutineContext: CoroutineContext, throwable: Throwable?) { - println("!!! CoroutinesStore error: ${throwable?.message}") - throwable?.printStackTrace() -// internalRouter.openErrorsScreen( -// title = resourceManager.getString(R.string.common_error_general_title), -// message = resourceManager.getString(R.string.common_error_network) -// ) + internalPoolsRouter.openErrorsScreen( + title = resourceManager.getString(R.string.common_error_general_title), + message = resourceManager.getString(R.string.common_error_network) + ) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index c8fece18ac..a360197128 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp -import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -58,7 +57,6 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails.PoolDetailsS import jp.co.soramitsu.liquiditypools.impl.presentation.poollist.PoolListScreen import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.NavAction -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 0da72e1b44..9e9ca4e1b5 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -37,7 +37,6 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.NavAction -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -48,7 +47,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.math.BigDecimal import javax.inject.Inject -import jp.co.soramitsu.common.resources.ResourceManager +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData @HiltViewModel class PoolsFlowViewModel @Inject constructor( @@ -62,8 +61,7 @@ class PoolsFlowViewModel @Inject constructor( private val coroutinesStore: CoroutinesStore, private val poolsInteractor: PoolsInteractor, private val internalPoolsRouter: InternalPoolsRouter, - private val poolsRouter: LiquidityPoolsRouter, - private val resourceManager: ResourceManager + private val poolsRouter: LiquidityPoolsRouter ) : BaseViewModel(), LiquidityAddCallbacks by liquidityAddPresenter, LiquidityAddConfirmCallbacks by liquidityAddConfirmPresenter, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 8cdf7237cd..cc070c15bb 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -11,7 +11,6 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope @@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData class AllPoolsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -103,10 +103,10 @@ class AllPoolsPresenter @Inject constructor( .onEach { commonPoolData: List -> coroutineScope { commonPoolData.forEach { pool -> - launch { - val baseTokenId = pool.basic.baseToken.currencyId ?: return@launch + launch pool@ { + val baseTokenId = pool.basic.baseToken.currencyId ?: return@pool val targetTokenId = - pool.basic.targetToken?.currencyId ?: return@launch + pool.basic.targetToken?.currencyId ?: return@pool val id = StringPair(baseTokenId, targetTokenId) val sbApy = poolsInteractor.getSbApy(pool.basic.reserveAccount) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 8d571fcc96..476f05e52e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -28,7 +28,6 @@ import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateAddLiquidityUseCase import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.models.WithDesired -import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor @@ -57,6 +56,7 @@ import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode import javax.inject.Inject +import jp.co.soramitsu.liquiditypools.impl.util.PolkaswapFormulas import kotlin.math.min class LiquidityAddPresenter @Inject constructor( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 2f7be3fcb0..07f330a62d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -21,12 +21,12 @@ import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.DemeterFarmingInteractor import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.domain.model.CommonUserPoolData import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateRemoveLiquidityUseCase +import jp.co.soramitsu.liquiditypools.impl.util.PolkaswapFormulas import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.CommonUserPoolData -import jp.co.soramitsu.polkaswap.impl.util.PolkaswapFormulas import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index c13dd485b2..7509457abc 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -32,8 +32,6 @@ import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.component.Notification import jp.co.soramitsu.common.compose.component.NotificationState import jp.co.soramitsu.common.compose.component.TitleValueViewState -import jp.co.soramitsu.common.compose.component.WarningInfo -import jp.co.soramitsu.common.compose.component.WarningInfoState import jp.co.soramitsu.common.compose.theme.backgroundBlack import jp.co.soramitsu.common.compose.theme.colorAccentDark import jp.co.soramitsu.common.compose.theme.grayButtonBackground diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index af06259330..c5f29954a9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails +import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair @@ -8,11 +9,10 @@ import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.formatPercent import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import jp.co.soramitsu.wallet.impl.domain.model.Token @@ -29,13 +29,11 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import javax.inject.Inject class PoolDetailsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, private val internalPoolsRouter: InternalPoolsRouter, private val walletInteractor: WalletInteractor, - private val chainsRepository: ChainsRepository, private val poolsInteractor: PoolsInteractor, private val accountInteractor: AccountInteractor, ) : PoolDetailsCallbacks { @@ -123,6 +121,6 @@ private fun CommonPoolData.mapToState(token: Token): PoolDetailsState { pooledTargetAmount = user?.targetPooled?.formatCrypto(basic.targetToken?.symbol).orEmpty(), pooledTargetFiat = user?.targetPooled?.applyFiatRate(token.fiatRate)?.formatFiat(token.fiatSymbol).orEmpty(), tvl = tvl?.formatFiat(token.fiatSymbol), - apy = null//"${basic.sbapy?.toBigDecimal()?.formatPercent()}%" + apy = null ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 0323b080e1..7ec993f270 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -1,18 +1,17 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.poollist -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor +import javax.inject.Inject import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData +import jp.co.soramitsu.liquiditypools.domain.model.isFilterMatch import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute -import jp.co.soramitsu.polkaswap.api.domain.models.CommonPoolData -import jp.co.soramitsu.polkaswap.api.domain.models.isFilterMatch -import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -30,15 +29,12 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update -import javax.inject.Inject class PoolListPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, private val internalPoolsRouter: InternalPoolsRouter, private val walletInteractor: WalletInteractor, - private val chainsRepository: ChainsRepository, - private val poolsInteractor: PoolsInteractor, - private val accountInteractor: AccountInteractor, + private val poolsInteractor: PoolsInteractor ) : PoolListScreenInterface { private val enteredAssetQueryFlow = MutableStateFlow("") @@ -68,7 +64,6 @@ class PoolListPresenter @Inject constructor( enteredAssetQueryFlow ) { pools, query -> coroutineScope { - val tokensDeferred = pools.map { async { walletInteractor.getToken(it.basic.baseToken) } } val tokensMap = tokensDeferred.awaitAll().associateBy { it.configuration.id } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt similarity index 98% rename from feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt rename to feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt index 38cd32d050..a585e79c37 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/util/PolkaswapFormulas.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt @@ -1,4 +1,4 @@ -package jp.co.soramitsu.polkaswap.impl.util +package jp.co.soramitsu.liquiditypools.impl.util import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.Big100 diff --git a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt b/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt deleted file mode 100644 index 61e5251d8b..0000000000 --- a/feature-polkaswap-api/src/main/kotlin/jp/co/soramitsu/polkaswap/api/sorablockexplorer/BlockExplorerManager.kt +++ /dev/null @@ -1,58 +0,0 @@ -package jp.co.soramitsu.polkaswap.api.sorablockexplorer - -import android.util.Log -import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.coroutines.CoroutineContext - -@Singleton -class BlockExplorerManager @Inject constructor(private val info: SoraWalletBlockExplorerInfo) { - - private val coroutineContext: CoroutineContext = Dispatchers.Default - private val coroutineScope = - CoroutineScope(coroutineContext + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> - println("!!! updateSbApyInternal error% ${throwable.message}") - }) - - private val apyDeferred = coroutineScope.async { info.getSpApy().associate { it.id to it.sbApy } } - - suspend fun syncSbApy() { -// apyDeferred.join() - val apy = apyDeferred.await() - Log.d("&&&", "synced sbApy: ${apy.size}") - } - - suspend fun getApy(id: String): Double? { - return apyDeferred.await()[id]?.times(100) - } - -// private val tempApy = mutableListOf() -// -// fun getTempApy(id: String) = tempApy.find { -// it.id == id -// }?.sbApy?.times(100) -// -// suspend fun updatePoolsSbApy() { -// updateSbApyInternal() -// } -// -// private suspend fun updateSbApyInternal() { -// runCatching { -// val response = info.getSpApy() -// println("!!! call blockExplorerManager.updatePoolsSbApy() result size = ${response.size}") -// tempApy.clear() -// tempApy.addAll(response) -// println("!!! call blockExplorerManager.updatePoolsSbApy() result updated") -// }.onFailure { -// println("!!! updateSbApyInternal error% ${it.message}") -// it.printStackTrace() -// } -// } - -} \ No newline at end of file diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt index 843115411a..46b7f16664 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/data/network/blockchain/Extrinsic.kt @@ -1,15 +1,11 @@ package jp.co.soramitsu.polkaswap.impl.data.network.blockchain -import java.math.BigDecimal +import java.math.BigInteger import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.DictEnum import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder -import java.math.BigInteger -import jp.co.soramitsu.common.utils.Modules -import jp.co.soramitsu.core.models.Asset -import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount fun ExtrinsicBuilder.swap( dexId: Int, @@ -50,120 +46,3 @@ fun ExtrinsicBuilder.swap( ) ) ) - -fun ExtrinsicBuilder.register( - dexId: Int, - baseAssetId: String, - targetAssetId: String -) = this.call( - "TradingPair", - "register", - mapOf( - "dex_id" to dexId.toBigInteger(), - "base_asset_id" to baseAssetId.mapCodeToken(), - "target_asset_id" to targetAssetId.mapCodeToken(), - ) -) - -fun ExtrinsicBuilder.initializePool( - dexId: Int, - baseAssetId: String, - targetAssetId: String -) = this.call( - Modules.POOL_XYK, - "initialize_pool", - mapOf( - "dex_id" to dexId.toBigInteger(), - "asset_a" to baseAssetId.mapCodeToken(), - "asset_b" to targetAssetId.mapCodeToken(), - ) -) - -fun ExtrinsicBuilder.depositLiquidity( - dexId: Int, - baseAssetId: String, - targetAssetId: String, - baseAssetAmount: BigInteger, - targetAssetAmount: BigInteger, - amountFromMin: BigInteger, - amountToMin: BigInteger -) = this.call( - Modules.POOL_XYK, - "deposit_liquidity", - mapOf( - "dex_id" to dexId.toBigInteger(), - "input_asset_a" to baseAssetId.mapCodeToken(), - "input_asset_b" to targetAssetId.mapCodeToken(), - "input_a_desired" to baseAssetAmount, - "input_b_desired" to targetAssetAmount, - "input_a_min" to amountFromMin, - "input_b_min" to amountToMin - ) -) - -fun ExtrinsicBuilder.removeLiquidity( - dexId: Int, - outputAssetIdA: String, - outputAssetIdB: String, - markerAssetDesired: BigInteger, - outputAMin: BigInteger, - outputBMin: BigInteger -) = - this.call( - Modules.POOL_XYK, - "withdraw_liquidity", - mapOf( - "dex_id" to dexId.toBigInteger(), - "output_asset_a" to outputAssetIdA.mapCodeToken(), - "output_asset_b" to outputAssetIdB.mapCodeToken(), - "marker_asset_desired" to markerAssetDesired, - "output_a_min" to outputAMin, - "output_b_min" to outputBMin - ) - ) - -fun ExtrinsicBuilder.liquidityAdd( - dexId: Int, - baseTokenId: String?, - targetTokenId: String?, - pairPresented: Boolean, - pairEnabled: Boolean, - tokenBaseAmount: BigInteger, - tokenTargetAmount: BigInteger, - amountBaseMin: BigInteger, - amountTargetMin: BigInteger -) { - if (baseTokenId != null && targetTokenId != null) { - if (!pairPresented) { - if (!pairEnabled) { - register( - dexId = dexId, - baseTokenId, - targetTokenId - ) - } - initializePool( - dexId = dexId, - baseTokenId, - targetTokenId - ) - } - - depositLiquidity( - dexId = dexId, - baseTokenId, - targetTokenId, - tokenBaseAmount, - tokenTargetAmount, - amountBaseMin, - amountTargetMin, - ) - } -} - -fun String.mapCodeToken() = Struct.Instance( - mapOf("code" to this.mapAssetId()) -) - -fun String.mapAssetId() = this.fromHex().mapAssetId() -fun ByteArray.mapAssetId() = this.toList().map { it.toInt().toBigInteger() } diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt index 19517782c9..4c2d4cfd4c 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/di/PolkaswapFeatureBindModule.kt @@ -9,7 +9,6 @@ import dagger.hilt.components.SingletonComponent import javax.inject.Named import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository -import jp.co.soramitsu.common.BuildConfig import jp.co.soramitsu.common.data.network.OptionsProvider import jp.co.soramitsu.common.data.network.config.RemoteConfigFetcher import jp.co.soramitsu.common.data.storage.Preferences @@ -22,13 +21,9 @@ import jp.co.soramitsu.polkaswap.impl.data.PolkaswapRepositoryImpl import jp.co.soramitsu.polkaswap.impl.domain.PolkaswapInteractorImpl import jp.co.soramitsu.runtime.di.REMOTE_STORAGE_SOURCE import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.runtime.IChainRegistry -import jp.co.soramitsu.coredb.AppDatabase import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.ChainDao -import jp.co.soramitsu.coredb.dao.PoolDao -import jp.co.soramitsu.polkaswap.api.sorablockexplorer.BlockExplorerManager import jp.co.soramitsu.runtime.multiNetwork.chain.ChainSyncService import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.runtime.multiNetwork.connection.ConnectionPool @@ -124,11 +119,6 @@ class PolkaswapFeatureModule { ) } -// @Singleton -// @Provides -// fun provideSoramitsuNetworkClient(): SoramitsuNetworkClient = -// SoramitsuNetworkClient(logging = BuildConfig.DEBUG, timeout = 20000) -// @Singleton @Provides fun provideSoraRemoteConfigBuilder( diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index f304e3f4ac..ef4998da9e 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -318,9 +318,7 @@ class BalanceListViewModel @Inject constructor( } if (it is NFTCollection.Loaded.WithFailure) { -// println("!!! NFTCollection.Loaded.WithFailure: ${it.chainName} : ${it.throwable.message} ") -// it.throwable.printStackTrace() -// chainsWithFailedRequests.add(it.chainName) + chainsWithFailedRequests.add(it.chainName) } if (it is NFTCollection.Loaded.Result) { From 03354c3f3cbdfd43beb2954ca933923324a0f1d8 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 19 Aug 2024 11:05:46 +0500 Subject: [PATCH 54/84] FLW-4821 We should replace the button --- .../impl/presentation/PoolsFlowFragment.kt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index a360197128..be4a6e4ec8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -264,9 +264,14 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment) { From 9bb30db4e240d2b4b73a9768b886345b880e05bd Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 19 Aug 2024 11:22:20 +0500 Subject: [PATCH 55/84] FLW-4872 There should be correct sorting of pools on the screen --- .../allpools/AllPoolsPresenter.kt | 20 ++++++++++++++----- .../poollist/PoolListPresenter.kt | 19 +++++++++++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index cc070c15bb..44d8351b90 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData class AllPoolsPresenter @Inject constructor( @@ -72,11 +73,20 @@ class AllPoolsPresenter @Inject constructor( it.user != null }.mapValues { it.value.sortedWith { current, next -> - val currentTvl = - current.basic.getTvl(tokensMap[current.basic.baseToken.id]?.fiatRate) - val nextTvl = - next.basic.getTvl(tokensMap[next.basic.baseToken.id]?.fiatRate) - compareNullDesc(currentTvl, nextTvl) + val currentTokenFiatRate = tokensMap[current.basic.baseToken.id]?.fiatRate + val nextTokenFiatRate = tokensMap[next.basic.baseToken.id]?.fiatRate + val userPoolData = current.user + val userPoolNextData = next.user + + if (userPoolData != null && userPoolNextData != null) { + val currentPooled = userPoolData.basePooled.applyFiatRate(currentTokenFiatRate) + val nextPooled = userPoolNextData.basePooled.applyFiatRate(nextTokenFiatRate) + compareNullDesc(currentPooled, nextPooled) + } else { + val currentTvl = current.basic.getTvl(currentTokenFiatRate) + val nextTvl = next.basic.getTvl(nextTokenFiatRate) + compareNullDesc(currentTvl, nextTvl) + } }.mapNotNull { commonPoolData -> val token = tokensMap[commonPoolData.basic.baseToken.id] commonPoolData.toListItemState(token) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 7ec993f270..8c101b3aca 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -4,6 +4,7 @@ import javax.inject.Inject import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData @@ -71,9 +72,21 @@ class PoolListPresenter @Inject constructor( pools.filter { it.basic.isFilterMatch(query) }.sortedWith { current, next -> - val currentTvl = current.basic.getTvl(tokensMap[current.basic.baseToken.id]?.fiatRate) - val nextTvl = next.basic.getTvl(tokensMap[next.basic.baseToken.id]?.fiatRate) - compareNullDesc(currentTvl, nextTvl) + val currentTokenFiatRate = tokensMap[current.basic.baseToken.id]?.fiatRate + val nextTokenFiatRate = tokensMap[next.basic.baseToken.id]?.fiatRate + val userPoolData = current.user + val userPoolNextData = next.user + + if (userPoolData != null && userPoolNextData != null) { + val currentPooled = userPoolData.basePooled.applyFiatRate(currentTokenFiatRate) + val nextPooled = userPoolNextData.basePooled.applyFiatRate(nextTokenFiatRate) + compareNullDesc(currentPooled, nextPooled) + } else { + val currentTvl = current.basic.getTvl(currentTokenFiatRate) + val nextTvl = next.basic.getTvl(nextTokenFiatRate) + compareNullDesc(currentTvl, nextTvl) + } + }.mapNotNull { it.toListItemState(tokensMap[it.basic.baseToken.id]) } } } From 9edcc16977044f0d20818a139dc30d6696d96f33 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 19 Aug 2024 15:52:59 +0500 Subject: [PATCH 56/84] FLW-4871 We should open main screen of pools after transaction --- .../liquidityaddconfirm/LiquidityAddConfirmPresenter.kt | 1 + .../liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 2508526d23..14829ffc50 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -218,6 +218,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( if (result.isNotEmpty()) { coroutinesStore.uiScope.launch { // internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) + internalPoolsRouter.back() internalPoolsRouter.back() internalPoolsRouter.back() internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.lp_liquidity_add_complete_text)) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 597bf663be..ff4f0294b9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -214,6 +214,7 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( if (result.isNotEmpty()) { coroutinesStore.uiScope.launch { // internalPoolsRouter.popupToScreen(LiquidityPoolsNavGraphRoute.PoolDetailsScreen) + internalPoolsRouter.back() internalPoolsRouter.back() internalPoolsRouter.back() internalPoolsRouter.openSuccessScreen(result, chainId, resourceManager.getString(R.string.lp_liquidity_add_complete_text)) From ed8493e8100edde19fc0adac48375fbf4f4c70e2 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Mon, 19 Aug 2024 21:50:17 +0500 Subject: [PATCH 57/84] FLW-4870 We should create P2R on the pool's screen for updates + improvements --- .../jp/co/soramitsu/coredb/dao/PoolDao.kt | 16 +++++++------- .../domain/interfaces/PoolsInteractor.kt | 4 +++- .../impl/domain/PoolsInteractorImpl.kt | 15 +++++++++---- .../impl/presentation/PoolsFlowFragment.kt | 13 ++++++------ .../impl/presentation/PoolsFlowViewModel.kt | 4 ---- .../allpools/AllPoolsPresenter.kt | 17 ++++++++++++++- .../presentation/allpools/AllPoolsScreen.kt | 17 ++++++++++++++- .../allpools/BasicPoolListItem.kt | 21 ++++++++++++------- .../LiquidityAddConfirmPresenter.kt | 5 +++++ .../LiquidityRemoveConfirmPresenter.kt | 5 +++++ 10 files changed, 86 insertions(+), 31 deletions(-) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt index dd284d0063..fe803062a3 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/PoolDao.kt @@ -15,7 +15,9 @@ interface PoolDao { companion object { private const val userPoolJoinBasic = """ - SELECT * FROM userpools left join allpools on userpools.userTokenIdBase=allpools.tokenIdBase and userpools.userTokenIdTarget=allpools.tokenIdTarget + SELECT * FROM userpools + left join allpools on userpools.userTokenIdBase = allpools.tokenIdBase + and userpools.userTokenIdTarget = allpools.tokenIdTarget """ } @@ -44,10 +46,11 @@ interface PoolDao { @Query( """ - select * from allpools a left join userpools u on - a.tokenIdBase = u.userTokenIdBase and a.tokenIdTarget = u.userTokenIdTarget - and u.accountAddress is not null - and u.accountAddress = :accountAddress + select * from allpools a + left join userpools u on a.tokenIdBase = u.userTokenIdBase + and a.tokenIdTarget = u.userTokenIdTarget + and u.accountAddress is not null + and u.accountAddress = :accountAddress """ ) fun subscribeAllPools(accountAddress: String?): Flow> @@ -66,7 +69,7 @@ interface PoolDao { and a.tokenIdTarget = u.userTokenIdTarget and u.accountAddress is not null and u.accountAddress = :accountAddress - where a.tokenIdBase=:baseTokenId and a.tokenIdTarget=:targetTokenId + where a.tokenIdBase = :baseTokenId and a.tokenIdTarget = :targetTokenId """ ) fun subscribePool(accountAddress: String, baseTokenId: String, targetTokenId: String): Flow @@ -79,5 +82,4 @@ interface PoolDao { @Upsert suspend fun insertUserPools(pools: List) - } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 65eae9290e..78ef8e1f5f 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -60,7 +60,9 @@ interface PoolsInteractor { slippageTolerance: Double ): String - suspend fun syncPools(chainId: ChainId) + suspend fun syncPools() + + suspend fun updateAccountPools() suspend fun observeRemoveLiquidity( chainId: ChainId, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 97af04dea3..c56a3ab54c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -174,15 +174,22 @@ class PoolsInteractorImpl( return status?.getOrNull() ?: "" } - override suspend fun syncPools(chainId: ChainId): Unit = withContext(Dispatchers.Default) { - val address = accountRepository.getSelectedAccount(chainId).address + override suspend fun syncPools(): Unit = withContext(Dispatchers.Default) { + val address = accountRepository.getSelectedAccount(poolsChainId).address supervisorScope { - launch { poolsRepository.updateBasicPools(chainId) } - launch { poolsRepository.updateAccountPools(chainId, address) } + launch { poolsRepository.updateBasicPools(poolsChainId) } + launch { poolsRepository.updateAccountPools(poolsChainId, address) } launch { blockExplorerManager.syncSbApy() } } } + override suspend fun updateAccountPools(): Unit = withContext(Dispatchers.Default) { + println("!!! updateAccountPools") + val address = accountRepository.getSelectedAccount(poolsChainId).address + poolsRepository.updateAccountPools(poolsChainId, address) + } + + override suspend fun getSbApy(id: String): Double? = withContext(coroutineContext) { blockExplorerManager.getApy(id) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index be4a6e4ec8..9511994fe0 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -49,6 +49,7 @@ import jp.co.soramitsu.common.presentation.InfoDialog import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreen +import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreenWithRefresh import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd.LiquidityAddScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm.LiquidityAddConfirmScreen import jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove.LiquidityRemoveScreen @@ -172,7 +173,7 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment) { - behavior.state = BottomSheetBehavior.STATE_EXPANDED - behavior.isHideable = true - behavior.skipCollapsed = true - } +// override fun setupBehavior(behavior: BottomSheetBehavior) { +// behavior.state = BottomSheetBehavior.STATE_EXPANDED +// behavior.isHideable = false +// behavior.skipCollapsed = true +// } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index 9e9ca4e1b5..01c8e24401 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -114,10 +114,6 @@ class PoolsFlowViewModel @Inject constructor( init { internalPoolsRouter.openAllPoolsScreen() - - launch { - poolsInteractor.syncPools(poolsInteractor.poolsChainId) - } } private val mutableToolbarStateFlow = MutableStateFlow>(LoadingState.Loading()) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 44d8351b90..7a07c85c55 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools +import androidx.lifecycle.viewModelScope import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair @@ -26,8 +27,12 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +import jp.co.soramitsu.common.utils.Event import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData +import jp.co.soramitsu.wallet.impl.data.network.blockchain.updaters.BalanceUpdateTrigger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class AllPoolsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -46,6 +51,8 @@ class AllPoolsPresenter @Inject constructor( chainsRepository.getChain(poolsInteractor.poolsChainId) } + private val refresh = MutableStateFlow(Event(Unit)) + private val stateFlow = MutableStateFlow(AllPoolsState()) fun createScreenStateFlow(scope: CoroutineScope): StateFlow { @@ -55,6 +62,11 @@ class AllPoolsPresenter @Inject constructor( private fun subscribeScreenState(scope: CoroutineScope) { scope.launch { + refresh.onEach { + println("!!! REFRESH SYNC AllPoolsPresenter call") + poolsInteractor.syncPools() + }.launchIn(scope) + val currentAccount = accountInteractor.selectedMetaAccount() val address = currentAccount.address(chainDeferred.await()) ?: return@launch @@ -152,7 +164,6 @@ class AllPoolsPresenter @Inject constructor( } .launchIn(scope) } - } override fun onPoolClicked(pair: StringPair) { @@ -162,4 +173,8 @@ class AllPoolsPresenter @Inject constructor( override fun onMoreClick(isUserPools: Boolean) { internalPoolsRouter.openPoolListScreen(isUserPools) } + + override fun onRefresh() { + refresh.value = Event(Unit) + } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index c097c2e77d..74ca887675 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -37,6 +37,7 @@ import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.ui_core.resources.Dimens +import jp.co.soramitsu.wallet.impl.presentation.balance.list.PullRefreshBox data class AllPoolsState( val userPools: List = listOf(), @@ -49,8 +50,20 @@ data class AllPoolsState( interface AllPoolsScreenInterface { fun onPoolClicked(pair: StringPair) fun onMoreClick(isUserPools: Boolean) + fun onRefresh() } +@Composable +fun AllPoolsScreenWithRefresh( + state: AllPoolsState, + callback: AllPoolsScreenInterface +) { + PullRefreshBox( + onRefresh = callback::onRefresh + ) { + AllPoolsScreen(state = state, callback = callback) + } +} @Composable fun AllPoolsScreen( @@ -225,11 +238,13 @@ private fun PreviewAllPoolsScreen() { state = AllPoolsState( userPools = items, allPools = items, - isLoading = false + isLoading = true +// isLoading = false ), callback = object : AllPoolsScreenInterface { override fun onPoolClicked(pair: StringPair) {} override fun onMoreClick(isUserPools: Boolean) {} + override fun onRefresh() {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index e84710b5e2..77324fcaf7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -119,11 +120,15 @@ fun BasicPoolListItem( Row( modifier = Modifier .wrapContentHeight() - .fillMaxWidth(), +// .fillMaxWidth() + , + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Text( - modifier = Modifier.wrapContentHeight(), + modifier = Modifier + .weight(1f) + .wrapContentHeight(), color = white, style = MaterialTheme.customTypography.header6, text = state.text1, @@ -133,8 +138,7 @@ fun BasicPoolListItem( when (state.apy) { is LoadingState.Loaded -> Text( modifier = Modifier - .wrapContentHeight() - .weight(1f), + .wrapContentHeight(), color = colorAccentDark, style = MaterialTheme.customTypography.header6, text = "%s %s".format( @@ -145,10 +149,13 @@ fun BasicPoolListItem( textAlign = TextAlign.End ) - is LoadingState.Loading -> ShimmerB2(modifier.width(64.dp)) + is LoadingState.Loading -> Shimmer( + Modifier + .height(12.dp) + .width(100.dp) + ) null -> Unit } - } Row( modifier = Modifier @@ -241,7 +248,7 @@ fun BasicPoolShimmerItem( .width(100.dp) ) } - MarginVertical(margin = 8.dp) + MarginVertical(margin = 6.dp) Row( modifier = Modifier .wrapContentHeight() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 14829ffc50..26891e897a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -225,6 +225,11 @@ class LiquidityAddConfirmPresenter @Inject constructor( } } }.invokeOnCompletion { + coroutinesStore.ioScope.launch { + delay(700) + poolsInteractor.updateAccountPools() + } + coroutinesStore.uiScope.launch { delay(300) setButtonLoading(false) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index ff4f0294b9..d364d1df97 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -221,6 +221,11 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } } }.invokeOnCompletion { + coroutinesStore.ioScope.launch { + delay(700) + poolsInteractor.updateAccountPools() + } + coroutinesStore.uiScope.launch { delay(300) setButtonLoading(false) From c2e7780e6983aaad2da94b154c6597c1d784ccea Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 13:52:08 +0500 Subject: [PATCH 58/84] FLW-4866 The asset's icons changing very slowly --- .../common/compose/component/AmountInput.kt | 7 +- .../liquidityadd/LiquidityAddPresenter.kt | 143 ++++++++++-------- .../LiquidityAddConfirmScreen.kt | 21 +-- 3 files changed, 99 insertions(+), 72 deletions(-) diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt index f1dd324c74..caef1c8e3d 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/AmountInput.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage +import coil.compose.SubcomposeAsyncImage import com.valentinilk.shimmer.shimmer import java.math.BigDecimal import jp.co.soramitsu.common.R @@ -213,10 +213,11 @@ private fun RowScope.TokenIcon( .padding(2.dp) .align(CenterVertically) if (url != null) { - AsyncImage( + SubcomposeAsyncImage( + modifier = imageModifier, model = getImageRequest(LocalContext.current, url), contentDescription = null, - modifier = imageModifier + loading = { Shimmer(Modifier.size(28.dp)) } ) } else { Icon( diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 476f05e52e..048650081f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -1,11 +1,16 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.isZero import jp.co.soramitsu.common.base.errors.ValidationException import jp.co.soramitsu.common.compose.component.AmountInputViewState import jp.co.soramitsu.common.compose.component.FeeInfoViewState +import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.presentation.dataOrNull import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.Event import jp.co.soramitsu.common.utils.MAX_DECIMALS_8 @@ -19,18 +24,20 @@ import jp.co.soramitsu.common.utils.formatPercent import jp.co.soramitsu.common.utils.moreThanZero import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.requireValue -import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.feature_liquiditypools_impl.R import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.usecase.ValidateAddLiquidityUseCase +import jp.co.soramitsu.liquiditypools.impl.util.PolkaswapFormulas import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.polkaswap.api.models.WithDesired import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor +import jp.co.soramitsu.wallet.impl.domain.model.Asset +import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -53,11 +60,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject -import jp.co.soramitsu.liquiditypools.impl.util.PolkaswapFormulas -import kotlin.math.min +import jp.co.soramitsu.core.models.Asset as CoreAsset class LiquidityAddPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -108,35 +111,40 @@ class LiquidityAddPresenter @Inject constructor( old.ids.second == new.ids.second } - @OptIn(ExperimentalCoroutinesApi::class) - val assetsInPoolFlow = screenArgsFlow.flatMapLatest { screenArgs -> - val ids = screenArgs.ids - val chainId = poolsInteractor.poolsChainId - val chainAssets = chainsRepository.getChain(chainId).assets - val baseAsset = chainAssets.firstOrNull { it.currencyId == ids.first }?.let { - walletInteractor.getCurrentAssetOrNull(chainId, it.id) - } - val targetAsset = chainAssets.firstOrNull { it.currencyId == ids.second }?.let { - walletInteractor.getCurrentAssetOrNull(chainId, it.id) - } + private val loadingAssetsInPoolFlow = MutableStateFlow>>(LoadingState.Loading()) - val assetsFlow = flowOf { - if (baseAsset == null || targetAsset == null) { - null - } else { - baseAsset to targetAsset + private val tokensInPoolFlow = loadingAssetsInPoolFlow.filterIsInstance>>().map { + it.data.first.token.configuration to it.data.second.token.configuration + }.distinctUntilChanged() + + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) + private fun subscribeState(coroutineScope: CoroutineScope) { + screenArgsFlow.onEach { + loadingAssetsInPoolFlow.value = LoadingState.Loading() + }.flatMapLatest { screenArgs -> + val ids = screenArgs.ids + val chainId = poolsInteractor.poolsChainId + val chainAssets = chainsRepository.getChain(chainId).assets + val baseAsset = chainAssets.firstOrNull { it.currencyId == ids.first }?.let { + walletInteractor.getCurrentAssetOrNull(chainId, it.id) + } + val targetAsset = chainAssets.firstOrNull { it.currencyId == ids.second }?.let { + walletInteractor.getCurrentAssetOrNull(chainId, it.id) } - }.mapNotNull { it } - assetsFlow - }.distinctUntilChanged() + val assetsFlow = flowOf { + if (baseAsset == null || targetAsset == null) { + null + } else { + baseAsset to targetAsset + } + }.mapNotNull { it } - private val tokensInPoolFlow = assetsInPoolFlow.map { - it.first.token.configuration to it.second.token.configuration - }.distinctUntilChanged() + assetsFlow + }.onEach { + loadingAssetsInPoolFlow.value = LoadingState.Loaded(it) + }.launchIn(coroutineScope) - @OptIn(FlowPreview::class) - private fun subscribeState(coroutineScope: CoroutineScope) { enteredBaseAmountFlow .onEach { desired = WithDesired.INPUT @@ -180,7 +188,7 @@ class LiquidityAddPresenter @Inject constructor( }.debounce(200).flatMapLatest { combine( poolFlow, - assetsInPoolFlow, + loadingAssetsInPoolFlow, uiBaseAmountFlow, uiTargetAmountFlow, isBaseAmountFocused, @@ -189,29 +197,19 @@ class LiquidityAddPresenter @Inject constructor( feeInfoViewStateFlow, isButtonLoading, isCalculatingAmounts - ) { pool, (assetBase, assetTarget), baseShown, targetShown, baseFocused, targetFocused, slippage, feeInfo, isButtonLoading, isCalulatingAmount -> - val totalBaseCrypto = assetBase.total?.formatCrypto(assetBase.token.configuration.symbol).orEmpty() - val totalBaseFiat = assetBase.fiatAmount?.formatFiat(assetBase.token.fiatSymbol) - val argsBase = totalBaseCrypto + totalBaseFiat?.let { " ($it)" }.orEmpty() - val totalBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) - - val totalTargetCrypto = assetTarget.total?.formatCrypto(assetTarget.token.configuration.symbol).orEmpty() - val totalTargetFiat = assetTarget.fiatAmount?.formatFiat(assetTarget.token.fiatSymbol) - val argsTarget = totalTargetCrypto + totalTargetFiat?.let { " ($it)" }.orEmpty() - val totalTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) - - val isButtonEnabled = amountBase.moreThanZero() && - amountTarget.moreThanZero() && - feeInfo.feeAmount != null && - isCalculatingAmounts.value == null - - LiquidityAddState( - apy = poolsInteractor.getSbApy(pool.basic.reserveAccount)?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty(), - slippage = "$slippage%", - feeInfo = feeInfo, - buttonEnabled = isButtonEnabled, - buttonLoading = isButtonLoading, - baseAmountInputViewState = AmountInputViewState( + ) { pool, loadingAssetsState, baseShown, targetShown, baseFocused, targetFocused, slippage, feeInfo, isButtonLoading, isCalulatingAmount -> + val assetBase = loadingAssetsState.dataOrNull()?.first + val assetTarget = loadingAssetsState.dataOrNull()?.second + + val baseAmountInputViewState = if (assetBase == null) { + AmountInputViewState.defaultObj + } else { + val totalBaseCrypto = assetBase.total?.formatCrypto(assetBase.token.configuration.symbol).orEmpty() + val totalBaseFiat = assetBase.fiatAmount?.formatFiat(assetBase.token.fiatSymbol) + val argsBase = totalBaseCrypto + totalBaseFiat?.let { " ($it)" }.orEmpty() + val totalBaseBalance = resourceManager.getString(R.string.common_available_format, argsBase) + + AmountInputViewState( tokenName = assetBase.token.configuration.symbol, tokenImage = assetBase.token.configuration.iconUrl, totalBalance = totalBaseBalance, @@ -219,8 +217,18 @@ class LiquidityAddPresenter @Inject constructor( fiatAmount = baseShown.applyFiatRate(assetBase.token.fiatRate)?.formatFiat(assetBase.token.fiatSymbol), isFocused = baseFocused, isShimmerAmounts = isCalulatingAmount == WithDesired.OUTPUT - ), - targetAmountInputViewState = AmountInputViewState( + ) + } + + val targetAmountInputViewState = if (assetTarget == null) { + AmountInputViewState.defaultObj + } else { + val totalTargetCrypto = assetTarget.total?.formatCrypto(assetTarget.token.configuration.symbol).orEmpty() + val totalTargetFiat = assetTarget.fiatAmount?.formatFiat(assetTarget.token.fiatSymbol) + val argsTarget = totalTargetCrypto + totalTargetFiat?.let { " ($it)" }.orEmpty() + val totalTargetBalance = resourceManager.getString(R.string.common_available_format, argsTarget) + + AmountInputViewState( tokenName = assetTarget.token.configuration.symbol, tokenImage = assetTarget.token.configuration.iconUrl, totalBalance = totalTargetBalance, @@ -229,6 +237,21 @@ class LiquidityAddPresenter @Inject constructor( isFocused = targetFocused, isShimmerAmounts = isCalulatingAmount == WithDesired.INPUT ) + } + + val isButtonEnabled = amountBase.moreThanZero() && + amountTarget.moreThanZero() && + feeInfo.feeAmount != null && + isCalculatingAmounts.value == null + + LiquidityAddState( + apy = poolsInteractor.getSbApy(pool.basic.reserveAccount)?.toBigDecimal()?.formatPercent()?.let { "$it%" }.orEmpty(), + slippage = "$slippage%", + feeInfo = feeInfo, + buttonEnabled = isButtonEnabled, + buttonLoading = isButtonLoading, + baseAmountInputViewState = baseAmountInputViewState, + targetAmountInputViewState = targetAmountInputViewState ) } }.stateIn(coroutineScope, SharingStarted.Lazily, LiquidityAddState()) @@ -260,7 +283,7 @@ class LiquidityAddPresenter @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) private suspend fun calculateAmount(): BigDecimal? { - val assets = assetsInPoolFlow.firstOrNull() + val assets = loadingAssetsInPoolFlow.firstOrNull()?.dataOrNull() val baseAmount = if (desired == WithDesired.INPUT) enteredBaseAmountFlow.value else enteredTargetAmountFlow.value val targetAmount = if (desired == WithDesired.INPUT) enteredTargetAmountFlow.value else enteredBaseAmountFlow.value @@ -340,8 +363,8 @@ class LiquidityAddPresenter @Inject constructor( } private suspend fun getLiquidityNetworkFee( - tokenBase: Asset, - tokenTarget: Asset, + tokenBase: CoreAsset, + tokenTarget: CoreAsset, tokenBaseAmount: BigDecimal, tokenToAmount: BigDecimal, pairEnabled: Boolean, @@ -379,7 +402,7 @@ class LiquidityAddPresenter @Inject constructor( val utilityAmount = walletInteractor.getCurrentAsset(chainId, utilityAssetId).total val feeAmount = networkFeeFlow.firstOrNull().orZero() - val poolAssets = assetsInPoolFlow.firstOrNull() ?: return@launch + val poolAssets = loadingAssetsInPoolFlow.firstOrNull()?.dataOrNull() ?: return@launch val validationResult = validateAddLiquidityUseCase( assetBase = poolAssets.first, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 62eedbc45a..0581afc5a7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import coil.compose.AsyncImage +import coil.compose.SubcomposeAsyncImage import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.B1 import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder @@ -35,6 +35,7 @@ import jp.co.soramitsu.common.compose.component.InfoTableItem import jp.co.soramitsu.common.compose.component.InfoTableItemAsset import jp.co.soramitsu.common.compose.component.MarginHorizontal import jp.co.soramitsu.common.compose.component.MarginVertical +import jp.co.soramitsu.common.compose.component.Shimmer import jp.co.soramitsu.common.compose.component.TitleIconValueState import jp.co.soramitsu.common.compose.component.TitleValueViewState import jp.co.soramitsu.common.compose.component.getImageRequest @@ -86,21 +87,23 @@ fun LiquidityAddConfirmScreen( MarginVertical(margin = 16.dp) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - AsyncImage( - model = getImageRequest(LocalContext.current, state.assetBase?.iconUrl.orEmpty()), - contentDescription = null, + SubcomposeAsyncImage( modifier = Modifier .size(64.dp) .offset(x = 14.dp) - .zIndex(1f) - ) - AsyncImage( - model = getImageRequest(LocalContext.current, state.assetTarget?.iconUrl.orEmpty()), + .zIndex(1f), + model = getImageRequest(LocalContext.current, state.assetBase?.iconUrl.orEmpty()), contentDescription = null, + loading = { Shimmer(Modifier.size(64.dp)) } + ) + SubcomposeAsyncImage( modifier = Modifier .size(64.dp) .offset(x = (-14).dp) - .zIndex(0f) + .zIndex(0f), + model = getImageRequest(LocalContext.current, state.assetTarget?.iconUrl.orEmpty()), + contentDescription = null, + loading = { Shimmer(Modifier.size(64.dp)) } ) } From b893cc846c41f67c8b16aecb10e14efd6fc39a6e Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 14:33:57 +0500 Subject: [PATCH 59/84] all pools list - items to fit the screen --- .../impl/domain/PoolsInteractorImpl.kt | 1 - .../allpools/AllPoolsPresenter.kt | 45 ++++++++++++++----- .../presentation/allpools/AllPoolsScreen.kt | 37 ++++++++++++--- .../allpools/BasicPoolListItem.kt | 5 +-- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index c56a3ab54c..97245efc04 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -184,7 +184,6 @@ class PoolsInteractorImpl( } override suspend fun updateAccountPools(): Unit = withContext(Dispatchers.Default) { - println("!!! updateAccountPools") val address = accountRepository.getSelectedAccount(poolsChainId).address poolsRepository.updateAccountPools(poolsChainId, address) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index 7a07c85c55..e3ac824d4d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -1,13 +1,18 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools -import androidx.lifecycle.viewModelScope +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.presentation.LoadingState +import jp.co.soramitsu.common.utils.Event +import jp.co.soramitsu.common.utils.applyFiatRate import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor +import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter @@ -26,13 +31,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import javax.inject.Inject -import jp.co.soramitsu.common.utils.Event -import jp.co.soramitsu.common.utils.applyFiatRate -import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData -import jp.co.soramitsu.wallet.impl.data.network.blockchain.updaters.BalanceUpdateTrigger -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext class AllPoolsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -52,6 +50,8 @@ class AllPoolsPresenter @Inject constructor( } private val refresh = MutableStateFlow(Event(Unit)) + private val screenHeight = MutableStateFlow(0.dp) + private val groupHeight = MutableStateFlow(0.dp) private val stateFlow = MutableStateFlow(AllPoolsState()) @@ -63,7 +63,6 @@ class AllPoolsPresenter @Inject constructor( private fun subscribeScreenState(scope: CoroutineScope) { scope.launch { refresh.onEach { - println("!!! REFRESH SYNC AllPoolsPresenter call") poolsInteractor.syncPools() }.launchIn(scope) @@ -107,8 +106,24 @@ class AllPoolsPresenter @Inject constructor( val userPools = poolLists[true].orEmpty() val otherPools = poolLists[false].orEmpty() - val shownUserPools = userPools.take(5) - val shownOtherPools = otherPools.take(10) + val screenHeight = screenHeight.value + val groupHeight = groupHeight.value + val itemHeight = 40.dp + val margin = 16.dp + + val availableHeight = screenHeight - groupHeight - margin * 2 - if (userPools.isNotEmpty()) { + groupHeight + margin + } else { + 0.dp + } + + val totalItems = (availableHeight / (itemHeight + margin)).toInt() + val maxGroupItems = totalItems / 2 + + val shownUserPools = userPools.take(maxGroupItems) + val userGroupItems = shownUserPools.size + val shownOtherPools = otherPools.take(totalItems - userGroupItems) + val hasExtraUserPools = shownUserPools.size < userPools.size val hasExtraAllPools = shownOtherPools.size < otherPools.size @@ -177,4 +192,12 @@ class AllPoolsPresenter @Inject constructor( override fun onRefresh() { refresh.value = Event(Unit) } + + override fun onWindowHeightChange(heightIs: Dp) { + screenHeight.value = heightIs + } + + override fun onHeaderHeightChange(heightIs: Dp) { + groupHeight.value = heightIs + } } \ No newline at end of file diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 74ca887675..3a74e863a5 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -18,13 +18,20 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.BackgroundCorneredWithBorder @@ -51,6 +58,8 @@ interface AllPoolsScreenInterface { fun onPoolClicked(pair: StringPair) fun onMoreClick(isUserPools: Boolean) fun onRefresh() + fun onWindowHeightChange(heightIs: Dp) + fun onHeaderHeightChange(heightIs: Dp) } @Composable @@ -70,8 +79,17 @@ fun AllPoolsScreen( state: AllPoolsState, callback: AllPoolsScreenInterface ) { + val localDensity = LocalDensity.current + var heightIs by remember { + mutableStateOf(0.dp) + } + Box( modifier = Modifier + .onGloballyPositioned { coordinates -> + heightIs = with(localDensity) { coordinates.size.height.toDp() } + callback.onWindowHeightChange(heightIs) + } .fillMaxSize() .nestedScroll(rememberNestedScrollInteropConnection()), ) { @@ -125,6 +143,10 @@ fun AllPoolsScreen( modifier = Modifier.wrapContentHeight() ) { PoolGroupHeader( + modifier = Modifier.onGloballyPositioned { coordinates -> + heightIs = with(localDensity) { coordinates.size.height.toDp() } + callback.onHeaderHeightChange(heightIs) + }, title = stringResource(id = R.string.lp_available_pools_title), onMoreClick = { callback.onMoreClick(false) }.takeIf { state.hasExtraAllPools } ) @@ -160,11 +182,11 @@ fun ShimmerPoolList(size: Int = 10) { } @Composable -private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { - Box(modifier = Modifier.wrapContentHeight()) { +private fun PoolGroupHeader(modifier: Modifier = Modifier, title: String, onMoreClick: (() -> Unit)?) { + Box(modifier = modifier.wrapContentHeight()) { Row( modifier = Modifier - .padding(vertical = Dimens.x1_5, horizontal = Dimens.x1_5) + .padding(Dimens.x1_5) .wrapContentHeight(), verticalAlignment = Alignment.CenterVertically ) { @@ -185,7 +207,7 @@ private fun PoolGroupHeader(title: String, onMoreClick: (() -> Unit)?) { shape = CircleShape, ) .clickable(onClick = onMoreClick) - .padding(all = Dimens.x1) + .padding(horizontal = Dimens.x1, vertical = 5.5.dp) ) { Text( text = stringResource(id = R.string.common_more).uppercase(), @@ -238,13 +260,16 @@ private fun PreviewAllPoolsScreen() { state = AllPoolsState( userPools = items, allPools = items, - isLoading = true -// isLoading = false +// isLoading = true + isLoading = false, + hasExtraUserPools = true ), callback = object : AllPoolsScreenInterface { override fun onPoolClicked(pair: StringPair) {} override fun onMoreClick(isUserPools: Boolean) {} override fun onRefresh() {} + override fun onWindowHeightChange(heightIs: Dp) {} + override fun onHeaderHeightChange(heightIs: Dp) {} }, ) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index 77324fcaf7..b5480e417a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -118,10 +118,7 @@ fun BasicPoolListItem( verticalArrangement = Arrangement.SpaceBetween, ) { Row( - modifier = Modifier - .wrapContentHeight() -// .fillMaxWidth() - , + modifier = Modifier.wrapContentHeight(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { From cd5e87e0b4d903794c6fb51c537a68b2ad0c2204 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 15:34:10 +0500 Subject: [PATCH 60/84] merge fix --- .../main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt | 2 +- .../liquiditypools/impl/presentation/PoolsFlowFragment.kt | 2 +- gradle/libs.versions.toml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt index 96dcc518c9..06caa35398 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt @@ -19,7 +19,7 @@ val Migration_68_69 = object : Migration(68, 69) { `avgTransactionTimeInHours` REAL NOT NULL, `maxTransactionTimeInHours` REAL NOT NULL, `minTransactionTimeInHours` REAL NOT NULL, - `scoredAt` STRING NOT NULL + `scoredAt` STRING NOT NULL, PRIMARY KEY(`metaId`), FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 9511994fe0..71029bd9b9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -159,7 +159,7 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment -> MainToolbarShimmer( - homeIconState = ToolbarHomeIconState() + homeIconState = ToolbarHomeIconState.Navigation(jp.co.soramitsu.feature_wallet_impl.R.drawable.ic_arrow_back_24dp) ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a3a3216ae..87571d6238 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanistVersion = "0.34.0" activityCompose = "1.8.2" -android_plugin = "8.5.0" +android_plugin = "8.5.2" appcompat = "1.6.1" architectureComponentVersion = "2.7.0" beaconVersion = "3.2.4" @@ -170,7 +170,6 @@ detekt-cli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = sharedFeaturesCoreDep = { module = "jp.co.soramitsu.shared_features:core", version.ref = "sharedFeaturesVersion" } sharedFeaturesXcmDep = { module = "jp.co.soramitsu.shared_features:xcm", version.ref = "sharedFeaturesVersion" } sharedFeaturesBackupDep = { module = "jp.co.soramitsu.shared_features:backup", version.ref = "sharedFeaturesVersion" } -#sharedFeaturesPoolsDep = { module = "jp.co.soramitsu.shared_features:polkaswap", version.ref = "sharedFeaturesVersion" } beacon-android-sdk = { module = "com.github.airgap-it:beacon-android-sdk", version.ref = "beaconVersion" } web3jDep = { module = "org.web3j:core", version.ref = "web3j" } From 827cdfca322f1e650d2d86ce53f75f3c1e9eb06b Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 15:40:47 +0500 Subject: [PATCH 61/84] version up 3.6.2 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c17b46cf72..b44dfefdcd 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ apply plugin: "org.sonarqube" buildscript { ext { // App version - versionName = '3.6.1' - versionCode = 192 + versionName = '3.6.2' + versionCode = 193 // SDK and tools compileSdkVersion = 34 From d4fd983161df64f0f013cb1d9cf4cb28f619e8ae Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 18:07:05 +0500 Subject: [PATCH 62/84] detekt fix wip --- .../blockexplorer/BlockExplorerManager.kt | 16 +++++---- .../data/DemeterFarmingRepository.kt | 4 +-- .../liquiditypools/data/PoolsRepository.kt | 1 - .../domain/DemeterFarmingPool.kt | 4 +-- .../interfaces/DemeterFarmingInteractor.kt | 2 +- .../domain/model/BasicPoolData.kt | 2 +- .../domain/model/UserPoolData.kt | 2 +- .../navigation/InternalPoolsRouter.kt | 4 +-- .../navigation/LiquidityPoolsNavGraphRoute.kt | 4 +-- .../liquiditypools/navigation/NavAction.kt | 2 +- .../impl/data/DemeterFarmingRepositoryImpl.kt | 33 ++++++++++--------- .../impl/data/DemeterStorage.kt | 2 +- .../impl/data/PoolsRepositoryImpl.kt | 9 ++--- .../impl/data/network/Extrinsic.kt | 2 +- .../liquiditypools/impl/di/PoolsModule.kt | 13 +++++--- .../domain/DemeterFarmingInteractorImpl.kt | 2 +- .../impl/domain/PoolsInteractorImpl.kt | 7 ++-- .../navigation/InternalPoolsRouterImpl.kt | 13 ++++---- .../impl/presentation/CoroutinesStore.kt | 5 +-- .../impl/presentation/PoolsFlowFragment.kt | 5 --- .../impl/presentation/PoolsFlowViewModel.kt | 7 ++-- .../allpools/AllPoolsPresenter.kt | 30 ++++++++--------- .../allpools/BasicPoolListItem.kt | 6 +--- .../liquidityadd/LiquidityAddPresenter.kt | 13 ++++---- .../liquidityadd/LiquidityAddScreen.kt | 2 +- .../LiquidityAddConfirmPresenter.kt | 20 +++++------ .../LiquidityAddConfirmScreen.kt | 4 +-- .../LiquidityRemovePresenter.kt | 31 ++++++++++------- .../liquidityremove/LiquidityRemoveScreen.kt | 2 +- .../LiquidityRemoveConfirmPresenter.kt | 15 ++++----- .../LiquidityRemoveConfirmScreen.kt | 1 - .../pooldetails/PoolDetailsPresenter.kt | 4 +-- .../poollist/PoolListPresenter.kt | 14 ++++---- .../usecase/ValidateAddLiquidityUseCase.kt | 8 ++--- .../usecase/ValidateRemoveLiquidityUseCase.kt | 6 ++-- .../impl/util/PolkaswapFormulas.kt | 16 ++++++--- .../jp/co/soramitsu/nft/impl/di/NFTModule.kt | 2 +- 37 files changed, 157 insertions(+), 156 deletions(-) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt index ff811114f8..6010c026ef 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/blockexplorer/BlockExplorerManager.kt @@ -1,24 +1,26 @@ package jp.co.soramitsu.liquiditypools.blockexplorer import android.util.Log -import javax.inject.Inject -import javax.inject.Singleton import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.CoroutineContext @Singleton class BlockExplorerManager @Inject constructor(private val info: SoraWalletBlockExplorerInfo) { private val coroutineContext: CoroutineContext = Dispatchers.Default private val coroutineScope = - CoroutineScope(coroutineContext + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> - Log.e("BlockExplorerManager", throwable.message.orEmpty()) - }) + CoroutineScope( + coroutineContext + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> + Log.e("BlockExplorerManager", throwable.message.orEmpty()) + } + ) private val apyDeferred = coroutineScope.async { info.getSpApy().associate { it.id to it.sbApy } } @@ -29,4 +31,4 @@ class BlockExplorerManager @Inject constructor(private val info: SoraWalletBlock suspend fun getApy(id: String): Double? { return apyDeferred.await()[id]?.times(100) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt index 276f8a0c13..94f92fe889 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/DemeterFarmingRepository.kt @@ -3,5 +3,5 @@ package jp.co.soramitsu.liquiditypools.data import jp.co.soramitsu.liquiditypools.domain.DemeterFarmingPool interface DemeterFarmingRepository { - suspend fun getFarmedPools(soraAccountAddress: String): List? -} \ No newline at end of file + suspend fun getFarmedPools(chainId: String): List? +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt index f3fbc2e529..42966f399c 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -79,5 +79,4 @@ interface PoolsRepository { fun subscribePools(address: String): Flow> fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow - } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt index 7966e89aa0..3dcf7eff82 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/DemeterFarmingPool.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.liquiditypools.domain -import java.math.BigDecimal import jp.co.soramitsu.wallet.impl.domain.model.Asset +import java.math.BigDecimal data class DemeterFarmingPool( val tokenBase: Asset, @@ -19,4 +19,4 @@ data class DemeterFarmingBasicPool( val apr: Double, val tvl: BigDecimal, val fee: Double, -) \ No newline at end of file +) diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt index 0f9d052b75..c3e0329462 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/DemeterFarmingInteractor.kt @@ -5,4 +5,4 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId interface DemeterFarmingInteractor { suspend fun getFarmedPools(chainId: ChainId): List? -} \ No newline at end of file +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt index 16afb0cea6..700923b122 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/BasicPoolData.kt @@ -14,4 +14,4 @@ data class BasicPoolData( fun getTvl(baseTokenFiatRate: BigDecimal?): BigDecimal? { return baseTokenFiatRate?.times(BigDecimal(2))?.multiply(baseReserves) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt index 3047f5670c..b931cf3daa 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/model/UserPoolData.kt @@ -21,7 +21,7 @@ data class UserPoolData( fun BasicPoolData.isFilterMatch(filter: String): Boolean { val t1 = - targetToken?.symbol?.lowercase()?.contains(filter.lowercase()) == true || + targetToken?.symbol?.lowercase()?.contains(filter.lowercase()) == true || targetToken?.currencyId?.lowercase()?.contains(filter.lowercase()) == true val t2 = baseToken.symbol.lowercase().contains(filter.lowercase()) || diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 781bc65118..5e5ba0046d 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -1,9 +1,9 @@ package jp.co.soramitsu.liquiditypools.navigation -import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow +import java.math.BigDecimal interface InternalPoolsRouter { fun createNavGraphRoutesFlow(): Flow @@ -34,4 +34,4 @@ interface InternalPoolsRouter { fun openInfoScreen(itemId: Int) fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) fun destination(clazz: Class): T? -} \ No newline at end of file +} diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt index 4cf6529b09..1b950bec54 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/LiquidityPoolsNavGraphRoute.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.liquiditypools.navigation -import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.StringPair +import java.math.BigDecimal sealed interface LiquidityPoolsNavGraphRoute { @@ -11,7 +11,7 @@ sealed interface LiquidityPoolsNavGraphRoute { override val routeName: String = "Loading" } - class AllPoolsScreen: LiquidityPoolsNavGraphRoute by Companion { + class AllPoolsScreen : LiquidityPoolsNavGraphRoute by Companion { companion object : LiquidityPoolsNavGraphRoute { override val routeName: String = "AllPoolsScreen" } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt index f261fbf805..89c35dd33b 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/NavAction.kt @@ -12,4 +12,4 @@ sealed interface NavAction { val title: String, val message: String ) : NavAction -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt index 86bdae8927..b1cd5dee8a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.data -import java.math.BigDecimal -import java.math.BigInteger -import java.util.concurrent.ConcurrentHashMap import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.androidfoundation.format.addHexPrefix @@ -31,6 +28,9 @@ import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojo import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.Asset +import java.math.BigDecimal +import java.math.BigInteger +import java.util.concurrent.ConcurrentHashMap class DemeterFarmingRepositoryImpl( private val chainRegistry: ChainRegistry, @@ -47,9 +47,7 @@ class DemeterFarmingRepositoryImpl( private val cachedFarmedPools = ConcurrentHashMap>() private var cachedFarmedBasicPools: List? = null - override suspend fun getFarmedPools( - chainId: String, - ): List? { + override suspend fun getFarmedPools(chainId: String): List? { val soraAccountAddress = accountRepository.getSelectedAccount(chainId).address if (cachedFarmedPools.containsKey(soraAccountAddress)) return cachedFarmedPools[soraAccountAddress] @@ -120,10 +118,15 @@ class DemeterFarmingRepositoryImpl( } else { total.times(poolTokenPrice) } - val apr = if (tvl.isZero()) BigDecimal.ZERO else emission - .times(BLOCKS_PER_YEAR.toBigDecimal()) - .times(rewardTokenPrice) - .div(tvl).times(100.toBigDecimal()) + + val apr = if (tvl.isZero()) { + BigDecimal.ZERO + } else { + emission + .times(BLOCKS_PER_YEAR.toBigDecimal()) + .times(rewardTokenPrice) + .div(tvl).times(100.toBigDecimal()) + } DemeterFarmingBasicPool( tokenBase = baseTokenMapped, @@ -163,7 +166,7 @@ class DemeterFarmingRepositoryImpl( val chain = chainRegistry.getChain(chainId) val storage = runtime.metadata.module("DemeterFarmingPlatform") - .storage("TokenInfos") + .storage("TokenInfos") val type = storage.type.value ?: return emptyList() val storageKey = storage.storageKey( runtime @@ -196,7 +199,7 @@ class DemeterFarmingRepositoryImpl( private suspend fun getAllFarms(chainId: ChainId): List { val runtime = chainRegistry.getRuntimeOrNull(chainId) ?: return emptyList() val storage = runtime.metadata.module("DemeterFarmingPlatform") - .storage("Pools") + .storage("Pools") val type = storage.type.value ?: return emptyList() val storageKey = storage.storageKey( runtime, @@ -252,10 +255,11 @@ class DemeterFarmingRepositoryImpl( return allocation.times(tokenPerBlock).times(multiplier) } + @Suppress("ComplexCondition") private suspend fun getDemeter(chainId: ChainId, address: String): List? { val runtime = chainRegistry.getRuntimeOrNull(chainId) ?: return emptyList() val storage = runtime.metadata.module("DemeterFarmingPlatform") - .storage("UserInfos") + .storage("UserInfos") val storageKey = storage.storageKey( runtime, address.toAccountId(), @@ -306,5 +310,4 @@ class DemeterFarmingRepositoryImpl( fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() fun String.assetIdFromKey(pos: Int): String = this.substring(0, this.length - (64 * pos)).assetIdFromKey() - -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt index 599b2bbfdf..ec80243c9a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterStorage.kt @@ -34,4 +34,4 @@ class DemeterStorage( val farm: Boolean, val amount: BigInteger, val rewardAmount: BigInteger, -) \ No newline at end of file +) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index e28081b66c..b9c87f057f 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -1,9 +1,6 @@ package jp.co.soramitsu.liquiditypools.impl.data import androidx.room.withTransaction -import java.math.BigDecimal -import java.math.BigInteger -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.androidfoundation.format.addHexPrefix @@ -73,6 +70,9 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.supervisorScope +import java.math.BigDecimal +import java.math.BigInteger +import javax.inject.Inject class PoolsRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, @@ -238,7 +238,6 @@ class PoolsRepositoryImpl @Inject constructor( val accountId = wallet.accountId(soraChain) val soraAssets = soraChain.assets - soraAssets.forEach { asset -> val currencyId = asset.currencyId val key = currencyId?.let { runtimeOrNull?.reservesKeyToken(it) } @@ -609,7 +608,6 @@ class PoolsRepositoryImpl @Inject constructor( ?.map { (it as BigInteger).toByte() } ?.toByteArray() - private suspend fun getUserPoolsTokenIdsKeys(chainId: ChainId, address: String): List { val accountPoolsKey = chainRegistry.getRuntimeOrNull(chainId)?.accountPoolsKey(address) return chainRegistry.awaitConnection(chainId).socketService.executeAsync( @@ -945,5 +943,4 @@ class PoolsRepositoryImpl @Inject constructor( user = userPoolData, ) } - } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt index cfca067e2e..68ca0cabbd 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt @@ -1,10 +1,10 @@ package jp.co.soramitsu.liquiditypools.impl.data.network -import java.math.BigInteger import jp.co.soramitsu.common.utils.Modules import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.shared_utils.runtime.extrinsic.ExtrinsicBuilder +import java.math.BigInteger fun ExtrinsicBuilder.register( dexId: Int, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index 0300c5b244..c6edf66a27 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -4,7 +4,6 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.common.resources.ResourceManager @@ -25,6 +24,7 @@ import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.presentation.WalletRouter +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -41,7 +41,10 @@ class PoolsModule { @Provides @Singleton - fun provideInternalLiquidityPoolsRouter(walletRouter: WalletRouter, resourceManager: ResourceManager): InternalPoolsRouter = InternalPoolsRouterImpl( + fun provideInternalLiquidityPoolsRouter( + walletRouter: WalletRouter, + resourceManager: ResourceManager + ): InternalPoolsRouter = InternalPoolsRouterImpl( walletRouter = walletRouter, resourceManager = resourceManager ) @@ -50,7 +53,7 @@ class PoolsModule { @Singleton fun provideDemeterFarmingInteractor( demeterFarmingRepository: DemeterFarmingRepository, - ) : DemeterFarmingInteractor = + ): DemeterFarmingInteractor = DemeterFarmingInteractorImpl(demeterFarmingRepository) @Provides @@ -61,7 +64,7 @@ class PoolsModule { accountRepository: AccountRepository, walletRepository: WalletRepository, poolsRepository: PoolsRepository, - ) : DemeterFarmingRepository = + ): DemeterFarmingRepository = DemeterFarmingRepositoryImpl( chainRegistry, bulkRetriever, @@ -89,4 +92,4 @@ class PoolsModule { appDataBase ) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt index 3b46047d67..1e8279469c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/DemeterFarmingInteractorImpl.kt @@ -12,4 +12,4 @@ class DemeterFarmingInteractorImpl( override suspend fun getFarmedPools(chainId: ChainId): List? { return demeterFarmingRepository.getFarmedPools(chainId) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 97245efc04..7aa46ce7f2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.domain -import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.data.secrets.v1.Keypair @@ -15,7 +14,6 @@ import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.domain.model.BasicPoolData import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -26,6 +24,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext +import java.math.BigDecimal +import kotlin.coroutines.CoroutineContext class PoolsInteractorImpl( private val poolsRepository: PoolsRepository, @@ -188,8 +188,7 @@ class PoolsInteractorImpl( poolsRepository.updateAccountPools(poolsChainId, address) } - - override suspend fun getSbApy(id: String): Double? = withContext(coroutineContext) { + override suspend fun getSbApy(id: String): Double? = withContext(coroutineContext) { blockExplorerManager.getApy(id) } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 631cdc714a..890b96e54c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -1,7 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.navigation -import java.math.BigDecimal -import java.util.Stack import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.feature_wallet_impl.R @@ -15,11 +13,13 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onEach +import java.math.BigDecimal +import java.util.Stack class InternalPoolsRouterImpl( private val walletRouter: WalletRouter, private val resourceManager: ResourceManager -): InternalPoolsRouter { +) : InternalPoolsRouter { private val routesStack = Stack() private val mutableActionsFlow = @@ -28,11 +28,12 @@ class InternalPoolsRouterImpl( private val mutableRoutesFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - override fun createNavGraphRoutesFlow(): Flow = mutableRoutesFlow.onEach { routesStack.push(it) } + override fun createNavGraphRoutesFlow(): Flow = mutableRoutesFlow.onEach { + routesStack.push(it) + } override fun createNavGraphActionsFlow(): Flow = mutableActionsFlow.onEach { if (it is NavAction.BackPressed && !routesStack.isEmpty()) routesStack.pop() } - override fun back() { mutableActionsFlow.tryEmit(NavAction.BackPressed) } @@ -106,4 +107,4 @@ class InternalPoolsRouterImpl( override fun destination(clazz: Class): T? { return routesStack.filterIsInstance(clazz).lastOrNull() } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt index fc9a1dcdf9..0c345de91c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/CoroutinesStore.kt @@ -2,6 +2,8 @@ package jp.co.soramitsu.liquiditypools.impl.presentation import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.cachedOrNew +import jp.co.soramitsu.feature_liquiditypools_impl.R +import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -9,8 +11,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import javax.inject.Inject import javax.inject.Singleton -import jp.co.soramitsu.feature_liquiditypools_impl.R -import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter import kotlin.coroutines.CoroutineContext import kotlin.properties.Delegates @@ -32,6 +32,7 @@ class CoroutinesStore @Inject constructor( return scope.coroutineContext[Job]?.isActive != true } + @Suppress("UnusedParameter") private fun handleException(coroutineContext: CoroutineContext, throwable: Throwable?) { internalPoolsRouter.openErrorsScreen( title = resourceManager.getString(R.string.common_error_general_title), diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt index 71029bd9b9..1a2ca4653c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowFragment.kt @@ -5,12 +5,10 @@ import android.app.Dialog import android.os.Build import android.os.Bundle import android.view.KeyEvent -import android.widget.FrameLayout import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -33,7 +31,6 @@ import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment @@ -170,7 +167,6 @@ class PoolsFlowFragment : BaseComposeBottomSheetDialogFragment = liquidityRemoveConfirmPresenter.createScreenStateFlow(coroutinesStore.uiScope) - val navGraphRoutesFlow: StateFlow = internalPoolsRouter.createNavGraphRoutesFlow().stateIn( scope = coroutinesStore.uiScope, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt index e3ac824d4d..5ac3d96469 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsPresenter.kt @@ -2,7 +2,6 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.allpools import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair @@ -16,21 +15,18 @@ import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.liquiditypools.impl.presentation.CoroutinesStore import jp.co.soramitsu.liquiditypools.impl.presentation.toListItemState import jp.co.soramitsu.liquiditypools.navigation.InternalPoolsRouter -import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject class AllPoolsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -41,10 +37,6 @@ class AllPoolsPresenter @Inject constructor( private val accountInteractor: AccountInteractor, ) : AllPoolsScreenInterface { - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() - .filterIsInstance() - .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - private val chainDeferred = coroutinesStore.uiScope.async { chainsRepository.getChain(poolsInteractor.poolsChainId) } @@ -140,7 +132,7 @@ class AllPoolsPresenter @Inject constructor( .onEach { commonPoolData: List -> coroutineScope { commonPoolData.forEach { pool -> - launch pool@ { + launch pool@{ val baseTokenId = pool.basic.baseToken.currencyId ?: return@pool val targetTokenId = pool.basic.targetToken?.currencyId ?: return@pool @@ -150,9 +142,13 @@ class AllPoolsPresenter @Inject constructor( stateFlow.update { prevState -> val newUserPools = prevState.userPools.map { if (it.ids == id) { - it.copy(apy = LoadingState.Loaded(sbApy?.let { apy -> + it.copy( + apy = LoadingState.Loaded( + sbApy?.let { apy -> "%s%%".format(apy.toBigDecimal().formatCrypto()) - }.orEmpty())) + }.orEmpty() + ) + ) } else { it } @@ -160,9 +156,13 @@ class AllPoolsPresenter @Inject constructor( val newAllPools = prevState.allPools.map { if (it.ids == id) { - it.copy(apy = LoadingState.Loaded(sbApy?.let { apy -> + it.copy( + apy = LoadingState.Loaded( + sbApy?.let { apy -> "%s%%".format(apy.toBigDecimal().formatCrypto()) - }.orEmpty())) + }.orEmpty() + ) + ) } else { it } @@ -200,4 +200,4 @@ class AllPoolsPresenter @Inject constructor( override fun onHeaderHeightChange(heightIs: Dp) { groupHeight.value = heightIs } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index b5480e417a..96bd9e436e 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -36,7 +35,6 @@ import coil.compose.SubcomposeAsyncImage import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.component.Shimmer -import jp.co.soramitsu.common.compose.component.ShimmerB2 import jp.co.soramitsu.common.compose.component.getImageRequest import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.models.retrieveString @@ -100,7 +98,7 @@ fun BasicPoolListItem( bottom.linkTo(parent.bottom, 2.dp) } .size(size = 32.dp), - model =getImageRequest(LocalContext.current, state.token2Icon), + model = getImageRequest(LocalContext.current, state.token2Icon), contentDescription = null, loading = { Shimmer(Modifier.size(Size.ExtraSmall)) }, error = { @@ -179,7 +177,6 @@ fun BasicPoolListItem( overflow = TextOverflow.Ellipsis, ) } - } } } @@ -267,7 +264,6 @@ fun BasicPoolShimmerItem( } } - @Preview @Composable private fun PreviewBasicPoolListItem() { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index 048650081f..f98f1ba79c 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityadd -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.isZero @@ -37,7 +34,6 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import jp.co.soramitsu.wallet.impl.domain.model.Asset -import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -60,6 +56,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import kotlin.math.min import jp.co.soramitsu.core.models.Asset as CoreAsset class LiquidityAddPresenter @Inject constructor( @@ -329,8 +329,7 @@ class LiquidityAddPresenter @Inject constructor( tokensInPoolFlow, stateSlippage, isPoolPairEnabled - ) - { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> + ) { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> getLiquidityNetworkFee( tokenBase = baseAsset, tokenTarget = targetAsset, @@ -481,4 +480,4 @@ class LiquidityAddPresenter @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index f60ef1532c..96144f4651 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import java.math.BigDecimal import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.AmountInput import jp.co.soramitsu.common.compose.component.AmountInputViewState @@ -40,6 +39,7 @@ import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_APY_ID import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID +import java.math.BigDecimal data class LiquidityAddState( val baseAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 26891e897a..895353f541 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -1,7 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityaddconfirm -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState @@ -40,6 +38,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject class LiquidityAddConfirmPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -63,12 +63,12 @@ class LiquidityAddConfirmPresenter @Inject constructor( val chainId = poolsInteractor.poolsChainId val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.first - && it.asset.token.configuration.chainId == chainId + it.asset.token.configuration.currencyId == ids.first && + it.asset.token.configuration.chainId == chainId } val secondInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.second - && it.asset.token.configuration.chainId == chainId + it.asset.token.configuration.currencyId == ids.second && + it.asset.token.configuration.chainId == chainId } if (firstInPair == null || secondInPair == null) { return@mapNotNull null @@ -121,7 +121,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( buttonEnabled = it.feeAmount.isNullOrEmpty().not() ) }.launchIn(coroutineScope) - } val networkFeeFlow = combine( @@ -129,8 +128,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( tokensInPoolFlow, stateSlippage, isPoolPairEnabled - ) - { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> + ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> getLiquidityNetworkFee( tokenBase = baseAsset.configuration, tokenTarget = targetAsset.configuration, @@ -146,7 +144,7 @@ class LiquidityAddConfirmPresenter @Inject constructor( private val feeInfoViewStateFlow: Flow = flowOf { requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> + }.flatMapLatest { utilityAssetId -> combine( networkFeeFlow, walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) @@ -246,4 +244,4 @@ class LiquidityAddConfirmPresenter @Inject constructor( buttonLoading = loading ) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 0581afc5a7..1b0684a9d4 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -165,7 +165,7 @@ fun LiquidityAddConfirmScreen( value = state.apy, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_APY_ID) ), - onClick = { callbacks.onAddItemClick(ITEM_APY_ID)} + onClick = { callbacks.onAddItemClick(ITEM_APY_ID) } ) InfoTableItemAsset( TitleIconValueState( @@ -181,7 +181,7 @@ fun LiquidityAddConfirmScreen( additionalValue = state.feeInfo.feeAmountFiat, clickState = TitleValueViewState.ClickState.Title(R.drawable.ic_info_14, ITEM_FEE_ID) ), - onClick = { callbacks.onAddItemClick(ITEM_FEE_ID)} + onClick = { callbacks.onAddItemClick(ITEM_FEE_ID) } ) MarginVertical(margin = 8.dp) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 07f330a62d..bab14c5ff7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremove -import java.math.BigDecimal -import java.math.RoundingMode -import javax.inject.Inject import jp.co.soramitsu.androidfoundation.format.isZero import jp.co.soramitsu.common.base.errors.ValidationException import jp.co.soramitsu.common.compose.component.FeeInfoViewState @@ -30,7 +27,6 @@ import jp.co.soramitsu.liquiditypools.navigation.LiquidityPoolsNavGraphRoute import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository import jp.co.soramitsu.wallet.api.domain.fromValidationResult import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor -import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -56,6 +52,10 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.math.RoundingMode +import javax.inject.Inject +import kotlin.math.min @OptIn(FlowPreview::class) class LiquidityRemovePresenter @Inject constructor( @@ -82,7 +82,6 @@ class LiquidityRemovePresenter @Inject constructor( private var poolDataReal: CommonUserPoolData? = null private var percent: Double = 0.0 - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() .onEach { @@ -152,8 +151,8 @@ class LiquidityRemovePresenter @Inject constructor( val chainId = poolsInteractor.poolsChainId val result = if (poolDataLocal != null) { val maxPercent = demeterFarmingInteractor.getFarmedPools(chainId)?.filter { pool -> - pool.tokenBase.token.configuration.currencyId == token1Id - && pool.tokenTarget.token.configuration.currencyId == token2Id + pool.tokenBase.token.configuration.currencyId == token1Id && + pool.tokenTarget.token.configuration.currencyId == token2Id }?.maxOfOrNull { PolkaswapFormulas.calculateShareOfPoolFromAmount( it.amount, @@ -194,17 +193,25 @@ class LiquidityRemovePresenter @Inject constructor( .collectLatest { poolDataLocal -> poolDataUsable = poolDataLocal amountBase = - if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( + if (poolDataLocal != null) { + PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.basePooled, percent, poolDataLocal.basic.baseToken.precision, - ) else BigDecimal.ZERO + ) + } else { + BigDecimal.ZERO + } amountTarget = - if (poolDataLocal != null) PolkaswapFormulas.calculateAmountByPercentage( + if (poolDataLocal != null) { + PolkaswapFormulas.calculateAmountByPercentage( poolDataLocal.user.targetPooled, percent, poolDataLocal.basic.targetToken?.precision!!, - ) else BigDecimal.ZERO + ) + } else { + BigDecimal.ZERO + } coroutinesStore.ioScope.launch { updateAmounts() @@ -542,4 +549,4 @@ class LiquidityRemovePresenter @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index 7509457abc..34fd485658 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import java.math.BigDecimal import jp.co.soramitsu.common.compose.component.AccentButton import jp.co.soramitsu.common.compose.component.AmountInput import jp.co.soramitsu.common.compose.component.AmountInputViewState @@ -40,6 +39,7 @@ import jp.co.soramitsu.common.compose.theme.white import jp.co.soramitsu.common.compose.theme.white08 import jp.co.soramitsu.feature_wallet_impl.R import jp.co.soramitsu.liquiditypools.impl.presentation.PoolsFlowViewModel.Companion.ITEM_FEE_ID +import java.math.BigDecimal data class LiquidityRemoveState( val baseAmountInputViewState: AmountInputViewState = AmountInputViewState.defaultObj, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index d364d1df97..2c3fb53a7a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -1,7 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.compose.component.FeeInfoViewState @@ -39,6 +37,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject class LiquidityRemoveConfirmPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -53,7 +53,6 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( private val _stateSlippage = MutableStateFlow(0.5) val stateSlippage = _stateSlippage.asStateFlow() - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) @@ -63,12 +62,12 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( val chainId = poolsInteractor.poolsChainId val assetsFlow = walletInteractor.assetsFlow().mapNotNull { val firstInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.first - && it.asset.token.configuration.chainId == chainId + it.asset.token.configuration.currencyId == ids.first && + it.asset.token.configuration.chainId == chainId } val secondInPair = it.firstOrNull { - it.asset.token.configuration.currencyId == ids.second - && it.asset.token.configuration.chainId == chainId + it.asset.token.configuration.currencyId == ids.second && + it.asset.token.configuration.chainId == chainId } if (firstInPair == null || secondInPair == null) { return@mapNotNull null @@ -242,4 +241,4 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( buttonLoading = loading ) } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt index 9f2afe94cd..fac72a3ec0 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -78,7 +78,6 @@ fun LiquidityRemoveConfirmScreen( .padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - BackgroundCorneredWithBorder( modifier = Modifier .fillMaxWidth() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt index c5f29954a9..e56bc8bc09 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsPresenter.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.pooldetails -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.androidfoundation.format.StringPair @@ -29,6 +28,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject class PoolDetailsPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -64,7 +64,6 @@ class PoolDetailsPresenter @Inject constructor( }.launchIn(coroutineScope) } - override fun onSupplyLiquidityClick() { coroutinesStore.ioScope.launch { val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch @@ -77,7 +76,6 @@ class PoolDetailsPresenter @Inject constructor( val ids = screenArgsFlow.replayCache.firstOrNull()?.ids ?: return@launch internalPoolsRouter.openRemoveLiquidityScreen(ids) } - } override fun onDetailItemClick(itemId: Int) { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt index 8c101b3aca..1de568cf4b 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListPresenter.kt @@ -1,6 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.poollist -import javax.inject.Inject import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.androidfoundation.format.compareNullDesc import jp.co.soramitsu.common.presentation.LoadingState @@ -30,6 +29,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update +import javax.inject.Inject class PoolListPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -86,7 +86,6 @@ class PoolListPresenter @Inject constructor( val nextTvl = next.basic.getTvl(nextTokenFiatRate) compareNullDesc(currentTvl, nextTvl) } - }.mapNotNull { it.toListItemState(tokensMap[it.basic.baseToken.id]) } } } @@ -121,15 +120,18 @@ class PoolListPresenter @Inject constructor( stateFlow.update { prevState -> val newPools = prevState.pools.map { pool -> apyMap.await()[pool.ids]?.let { sbApy -> - pool.copy(apy = LoadingState.Loaded(sbApy.let { apy -> + pool.copy( + apy = LoadingState.Loaded( + sbApy.let { apy -> "%s%%".format(apy.toBigDecimal().formatCrypto()) - })) + } + ) + ) } ?: pool } prevState.copy(pools = newPools) } - }.launchIn(coroutineScope) } @@ -140,4 +142,4 @@ class PoolListPresenter @Inject constructor( override fun onAssetSearchEntered(value: String) { enteredAssetQueryFlow.value = value } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index 72f94e82ff..9ff712898a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -1,10 +1,10 @@ package jp.co.soramitsu.liquiditypools.impl.usecase -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.wallet.api.domain.TransferValidationResult import jp.co.soramitsu.wallet.impl.domain.model.Asset +import java.math.BigDecimal +import javax.inject.Inject class ValidateAddLiquidityUseCase @Inject constructor() { @@ -32,7 +32,7 @@ class ValidateAddLiquidityUseCase @Inject constructor() { feeAmount < utilityAmount } - val validationChecks = mapOf ( + val validationChecks = mapOf( TransferValidationResult.InsufficientBalance to (!isEnoughAmountBase || !isEnoughAmountTarget), TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee ) @@ -50,4 +50,4 @@ class ValidateAddLiquidityUseCase @Inject constructor() { } return TransferValidationResult.Valid } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt index 344353145a..c193baa171 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt @@ -1,8 +1,8 @@ package jp.co.soramitsu.liquiditypools.impl.usecase +import jp.co.soramitsu.wallet.api.domain.TransferValidationResult import java.math.BigDecimal import javax.inject.Inject -import jp.co.soramitsu.wallet.api.domain.TransferValidationResult class ValidateRemoveLiquidityUseCase @Inject constructor() { @@ -21,7 +21,7 @@ class ValidateRemoveLiquidityUseCase @Inject constructor() { val isEnoughAmountFee = feeAmount < utilityAmount - val validationChecks = mapOf ( + val validationChecks = mapOf( TransferValidationResult.InsufficientBalance to (!isEnoughAmountBase || !isEnoughAmountTarget), TransferValidationResult.InsufficientUtilityAssetBalance to !isEnoughAmountFee ) @@ -39,4 +39,4 @@ class ValidateRemoveLiquidityUseCase @Inject constructor() { } return TransferValidationResult.Valid } -} \ No newline at end of file +} diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt index a585e79c37..2d06fa93c1 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt @@ -1,11 +1,11 @@ package jp.co.soramitsu.liquiditypools.impl.util -import java.math.BigDecimal import jp.co.soramitsu.androidfoundation.format.Big100 import jp.co.soramitsu.androidfoundation.format.divideBy import jp.co.soramitsu.androidfoundation.format.equalTo import jp.co.soramitsu.androidfoundation.format.safeDivide import jp.co.soramitsu.polkaswap.api.models.WithDesired +import java.math.BigDecimal object PolkaswapFormulas { @@ -13,7 +13,7 @@ object PolkaswapFormulas { reserves: BigDecimal, poolProvidersBalance: BigDecimal, totalIssuance: BigDecimal, - precision: Int? = 18 //OptionsProvider.defaultScale, + precision: Int? = 18 // OptionsProvider.defaultScale, ): BigDecimal = reserves.multiply(poolProvidersBalance).divideBy(totalIssuance, precision) @@ -26,8 +26,11 @@ object PolkaswapFormulas { fun calculateShareOfPoolFromAmount( amount: BigDecimal, amountPooled: BigDecimal, - ): Double = if (amount.equalTo(amountPooled)) 100.0 else + ): Double = if (amount.equalTo(amountPooled)) { + 100.0 + } else { calculateShareOfPool(amount, amountPooled).toDouble() + } fun calculateAddLiquidityAmount( baseAmount: BigDecimal, @@ -94,14 +97,17 @@ object PolkaswapFormulas { amount: BigDecimal, percentage: Double, precision: Int, - ): BigDecimal = if (percentage == 100.0) amount else + ): BigDecimal = if (percentage == 100.0) { + amount + } else { amount.multiply(percentage.toBigDecimal()) .safeDivide(Big100, precision) + } fun calculateOneAmountFromAnother( amount: BigDecimal, amountPooled: BigDecimal, otherPooled: BigDecimal, - precision: Int? = 18 //OptionsProvider.defaultScale, + precision: Int? = 18 // OptionsProvider.defaultScale, ): BigDecimal = amount.multiply(otherPooled).safeDivide(amountPooled, precision) } diff --git a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt index 43177a38b7..93f6054c32 100644 --- a/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt +++ b/feature-nft-impl/src/main/java/jp/co/soramitsu/nft/impl/di/NFTModule.kt @@ -5,7 +5,6 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.nft.data.NFTRepository @@ -38,6 +37,7 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) From 8bea0891f99f6f660c722a0d4e0331200ee66653 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 20:03:48 +0500 Subject: [PATCH 63/84] detekt fix --- .../src/main/kotlin/detekt-setup.gradle.kts | 1 + .../liquiditypools/data/PoolsRepository.kt | 15 +- .../domain/interfaces/PoolsInteractor.kt | 18 +- .../navigation/InternalPoolsRouter.kt | 15 +- .../impl/data/DemeterFarmingRepositoryImpl.kt | 19 +- .../impl/data/PoolsRepositoryImpl.kt | 126 +++----------- .../impl/data/network/Extrinsic.kt | 4 +- .../liquiditypools/impl/di/PoolsModule.kt | 22 +-- .../impl/domain/PoolsInteractorImpl.kt | 35 ++-- .../navigation/InternalPoolsRouterImpl.kt | 13 +- .../impl/presentation/PoolsFlowViewModel.kt | 12 +- .../presentation/allpools/AllPoolsScreen.kt | 18 +- .../allpools/BasicPoolListItem.kt | 4 +- .../liquidityadd/LiquidityAddPresenter.kt | 163 +++++++++--------- .../liquidityadd/LiquidityAddScreen.kt | 5 +- .../LiquidityAddConfirmPresenter.kt | 73 ++++---- .../LiquidityAddConfirmScreen.kt | 5 +- .../LiquidityRemovePresenter.kt | 154 +++++++++-------- .../liquidityremove/LiquidityRemoveScreen.kt | 5 +- .../LiquidityRemoveConfirmPresenter.kt | 63 +++---- .../LiquidityRemoveConfirmScreen.kt | 5 +- .../pooldetails/PoolDetailsScreen.kt | 5 +- .../presentation/poollist/PoolListScreen.kt | 10 +- .../usecase/ValidateAddLiquidityUseCase.kt | 4 +- .../usecase/ValidateRemoveLiquidityUseCase.kt | 4 +- .../impl/util/PolkaswapFormulas.kt | 33 ++-- 26 files changed, 373 insertions(+), 458 deletions(-) diff --git a/buildSrc/src/main/kotlin/detekt-setup.gradle.kts b/buildSrc/src/main/kotlin/detekt-setup.gradle.kts index c9d2418e66..5886ec4477 100644 --- a/buildSrc/src/main/kotlin/detekt-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/detekt-setup.gradle.kts @@ -33,6 +33,7 @@ fun Detekt.setup(autoCorrect: Boolean) { // TODO: Remove exclude paths after merge detekt to develop exclude( + "**/androidfoundation/**", "**/common/**", "**/core-api/**", "**/core-db/**", diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt index 42966f399c..7b501db2a8 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/data/PoolsRepository.kt @@ -8,6 +8,7 @@ import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import kotlinx.coroutines.flow.Flow import java.math.BigDecimal +@Suppress("ComplexInterface") interface PoolsRepository { val poolsChainId: String @@ -21,7 +22,11 @@ interface PoolsRepository { suspend fun getBasicPools(chainId: ChainId): List - suspend fun getBasicPool(chainId: ChainId, baseTokenId: String, targetTokenId: String): BasicPoolData? + suspend fun getBasicPool( + chainId: ChainId, + baseTokenId: String, + targetTokenId: String + ): BasicPoolData? suspend fun getUserPoolData( chainId: ChainId, @@ -30,6 +35,7 @@ interface PoolsRepository { targetTokenId: ByteArray ): PoolDataDto? + @Suppress("LongParameterList") suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, @@ -61,6 +67,7 @@ interface PoolsRepository { secondAmountMin: BigDecimal ): Result? + @Suppress("LongParameterList") suspend fun observeAddLiquidity( chainId: ChainId, address: String, @@ -78,5 +85,9 @@ interface PoolsRepository { suspend fun updateBasicPools(chainId: ChainId) fun subscribePools(address: String): Flow> - fun subscribePool(address: String, baseTokenId: String, targetTokenId: String): Flow + fun subscribePool( + address: String, + baseTokenId: String, + targetTokenId: String + ): Flow } diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt index 78ef8e1f5f..e29c9fac48 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/domain/interfaces/PoolsInteractor.kt @@ -8,6 +8,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigDecimal +@Suppress("ComplexInterface") interface PoolsInteractor { val poolsChainId: String @@ -15,10 +16,7 @@ interface PoolsInteractor { fun subscribePoolsCacheOfAccount(address: String): Flow> fun subscribePoolsCacheCurrentAccount(): Flow> - suspend fun getPoolData( - baseTokenId: String, - targetTokenId: String, - ): Flow + suspend fun getPoolData(baseTokenId: String, targetTokenId: String): Flow suspend fun getUserPoolData( chainId: ChainId, @@ -27,6 +25,7 @@ interface PoolsInteractor { tokenId: ByteArray ): PoolDataDto? + @Suppress("LongParameterList") suspend fun calcAddLiquidityNetworkFee( chainId: ChainId, address: String, @@ -39,16 +38,11 @@ interface PoolsInteractor { slippageTolerance: Double ): BigDecimal? - suspend fun calcRemoveLiquidityNetworkFee( - tokenBase: Asset, - tokenTarget: Asset, - ): BigDecimal? + suspend fun calcRemoveLiquidityNetworkFee(tokenBase: Asset, tokenTarget: Asset): BigDecimal? - suspend fun isPairEnabled( - baseTokenId: String, - targetTokenId: String - ): Boolean + suspend fun isPairEnabled(baseTokenId: String, targetTokenId: String): Boolean + @Suppress("LongParameterList") suspend fun observeAddLiquidity( chainId: ChainId, tokenBase: Asset, diff --git a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt index 5e5ba0046d..bfbecedc18 100644 --- a/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt +++ b/feature-liquiditypools-api/src/main/java/jp/co/soramitsu/liquiditypools/navigation/InternalPoolsRouter.kt @@ -5,6 +5,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow import java.math.BigDecimal +@Suppress("ComplexInterface") interface InternalPoolsRouter { fun createNavGraphRoutesFlow(): Flow fun createNavGraphActionsFlow(): Flow @@ -15,7 +16,12 @@ interface InternalPoolsRouter { fun openDetailsPoolScreen(ids: StringPair) fun openAddLiquidityScreen(ids: StringPair) - fun openAddLiquidityConfirmScreen(ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) + fun openAddLiquidityConfirmScreen( + ids: StringPair, + amountBase: BigDecimal, + amountTarget: BigDecimal, + apy: String + ) fun openRemoveLiquidityScreen(ids: StringPair) fun openRemoveLiquidityConfirmScreen( @@ -32,6 +38,11 @@ interface InternalPoolsRouter { fun openErrorsScreen(title: String? = null, message: String) fun openInfoScreen(title: String, message: String) fun openInfoScreen(itemId: Int) - fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) + fun openSuccessScreen( + txHash: String, + chainId: ChainId, + customMessage: String + ) + fun destination(clazz: Class): T? } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt index b1cd5dee8a..8c3aed02e8 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/DemeterFarmingRepositoryImpl.kt @@ -40,10 +40,6 @@ class DemeterFarmingRepositoryImpl( private val poolsRepository: PoolsRepository, ) : DemeterFarmingRepository { - companion object { - private const val BLOCKS_PER_YEAR = 5256000 - } - private val cachedFarmedPools = ConcurrentHashMap>() private var cachedFarmedBasicPools: List? = null @@ -240,7 +236,7 @@ class DemeterFarmingRepositoryImpl( precision: Int ): BigDecimal { val tokenMultiplier = - ((if (basic.isFarm) reward?.farmsTotalMultiplier else reward?.stakingTotalMultiplier))?.toBigDecimal( + (if (basic.isFarm) reward?.farmsTotalMultiplier else reward?.stakingTotalMultiplier)?.toBigDecimal( precision ) ?: BigDecimal.ZERO if (tokenMultiplier.isZero()) return BigDecimal.ZERO @@ -300,14 +296,17 @@ class DemeterFarmingRepositoryImpl( mapper = pojo(), ).result - fun Struct.Instance.mapToToken(field: String) = - this.get(field)?.getTokenId()?.toHexString(true) + fun Struct.Instance.mapToToken(field: String) = this.get(field)?.getTokenId()?.toHexString(true) fun Struct.Instance.getTokenId() = get>("code") ?.map { (it as BigInteger).toByte() } ?.toByteArray() - fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() - fun String.assetIdFromKey(pos: Int): String = - this.substring(0, this.length - (64 * pos)).assetIdFromKey() + fun String.assetIdFromKey() = this.takeLast(ASSET_SIZE).addHexPrefix() + fun String.assetIdFromKey(pos: Int): String = this.substring(0, this.length - ASSET_SIZE * pos).assetIdFromKey() + + companion object { + private const val BLOCKS_PER_YEAR = 5_256_000 + private const val ASSET_SIZE = 64 + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt index b9c87f057f..588327d2aa 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/PoolsRepositoryImpl.kt @@ -33,7 +33,6 @@ import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.runtime.multiNetwork.chain.model.soraMainChainId -import jp.co.soramitsu.runtime.network.subscriptionFlowCatching import jp.co.soramitsu.shared_utils.encrypt.keypair.Keypair import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.extensions.toHexString @@ -55,7 +54,6 @@ import jp.co.soramitsu.shared_utils.wsrpc.mappers.pojoList import jp.co.soramitsu.shared_utils.wsrpc.mappers.scale import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.RuntimeRequest import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.GetStorageRequest -import jp.co.soramitsu.shared_utils.wsrpc.request.runtime.storage.SubscribeStorageRequest import jp.co.soramitsu.shared_utils.wsrpc.subscription.response.SubscriptionChange import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount @@ -63,10 +61,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.supervisorScope @@ -74,6 +68,7 @@ import java.math.BigDecimal import java.math.BigInteger import javax.inject.Inject +@Suppress("LargeClass", "MagicNumber") class PoolsRepositoryImpl @Inject constructor( private val extrinsicService: ExtrinsicService, private val chainRegistry: ChainRegistry, @@ -111,13 +106,12 @@ class PoolsRepositoryImpl @Inject constructor( mapOf("code" to this.mapAssetId()) ) - fun RuntimeSnapshot.reservesKeyToken(baseTokenId: String): String = - this.metadata.module(Modules.POOL_XYK) - .storage("Reserves") - .storageKey( - this, - baseTokenId.mapCodeToken(), - ) + fun RuntimeSnapshot.reservesKeyToken(baseTokenId: String): String = this.metadata.module(Modules.POOL_XYK) + .storage("Reserves") + .storageKey( + this, + baseTokenId.mapCodeToken(), + ) suspend fun getStorageHex(chainId: ChainId, storageKey: String): String? = chainRegistry.awaitConnection(chainId).socketService.executeAsync( @@ -170,13 +164,10 @@ class PoolsRepositoryImpl @Inject constructor( fun String.assetIdFromKey() = this.takeLast(64).addHexPrefix() object TotalIssuance : Schema() { - val totalIssuance by uint128() + val value by uint128() } - suspend fun getPoolTotalIssuances( - chainId: ChainId, - reservesAccountId: ByteArray, - ): BigInteger? { + suspend fun getPoolTotalIssuances(chainId: ChainId, reservesAccountId: ByteArray): BigInteger? { val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storageKey = runtimeOrNull?.metadata?.module(Modules.POOL_XYK) ?.storage("TotalIssuances") @@ -188,7 +179,7 @@ class PoolsRepositoryImpl @Inject constructor( ) .result ?.let { storage -> - storage[storage.schema.totalIssuance] + storage[storage.schema.value] } } @@ -223,6 +214,7 @@ class PoolsRepositoryImpl @Inject constructor( ) } + @Suppress("NestedBlockDepth") override suspend fun getBasicPools(chainId: ChainId): List { val runtimeOrNull = chainRegistry.getRuntimeOrNull(chainId) val storage = runtimeOrNull?.metadata @@ -285,33 +277,6 @@ class PoolsRepositoryImpl @Inject constructor( return list } - private fun subscribeAccountPoolProviders( - chainId: ChainId, - address: String, - reservesAccount: ByteArray, - ): Flow = flow { - val poolProvidersKey = - chainRegistry.getRuntimeOrNull(chainId)?.let { - it.metadata.module(Modules.POOL_XYK) - .storage("TotalIssuances") - .storageKey( - it, - reservesAccount, - address.toAccountId() - ) - } ?: error("subscribeAccountPoolProviders poolProvidersKey is null") - val poolProvidersFlow = - chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(poolProvidersKey), - "state_unsubscribeStorage", - ).map { - it.map { it.storageChange().getSingleChange().orEmpty() } - }.map { - it.getOrNull().orEmpty() - } - emitAll(poolProvidersFlow) - } - override suspend fun getUserPoolData( chainId: ChainId, address: String, @@ -451,8 +416,7 @@ class PoolsRepositoryImpl @Inject constructor( } } - fun Struct.Instance.mapToToken(field: String) = - this.get(field)?.getTokenId()?.toHexString(true) + fun Struct.Instance.mapToToken(field: String) = this.get(field)?.getTokenId()?.toHexString(true) fun String.takeInt32() = uint32.fromHex(this.takeLast(8)).toInt() @@ -478,7 +442,7 @@ class PoolsRepositoryImpl @Inject constructor( } object PoolProviders : Schema() { - val poolProviders by uint128() + val value by uint128() } private suspend fun getPoolProviders( @@ -502,7 +466,7 @@ class PoolsRepositoryImpl @Inject constructor( ) .let { storage -> storage.result?.let { - it[it.schema.poolProviders] + it[it.schema.value] } ?: BigInteger.ZERO } }.getOrElse { @@ -543,7 +507,7 @@ class PoolsRepositoryImpl @Inject constructor( tokenId.mapCodeToken(), ) - @Suppress("UNCHECKED_CAST") + @Suppress("UNCHECKED_CAST", "ThrowsCount") fun SubscriptionChange.storageChange(): SubscribeStorageResult { val result = params.result as? Map<*, *> ?: throw IllegalArgumentException("${params.result} is not a valid storage result") @@ -556,49 +520,6 @@ class PoolsRepositoryImpl @Inject constructor( return SubscribeStorageResult(block, changes) } - private fun subscribeToPoolData( - chainId: ChainId, - baseTokenId: String, - tokenId: ByteArray, - reservesAccount: ByteArray, - ): Flow = flow { - val reservesKey = - chainRegistry.getRuntimeOrNull(chainId)?.reservesKey(baseTokenId, tokenId) - - val reservesFlow = reservesKey?.let { - chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(reservesKey), - "state_unsubscribeStorage", - ).map { - it.map { it.storageChange().getSingleChange().orEmpty() } - }.map { - it.getOrNull().orEmpty() - } - } ?: emptyFlow() - - val totalIssuanceKey = - chainRegistry.getRuntimeOrNull(chainId)?.let { - it.metadata.module(Modules.POOL_XYK) - .storage("TotalIssuances") - .storageKey(it, reservesAccount) - } - val totalIssuanceFlow = totalIssuanceKey?.let { - chainRegistry.getConnection(chainId).socketService.subscriptionFlowCatching( - SubscribeStorageRequest(totalIssuanceKey), - "state_unsubscribeStorage", - ).map { - it.getOrNull()?.storageChange()?.getSingleChange().orEmpty() - } - } ?: emptyFlow() - - val resultFlow = reservesFlow - .combine(totalIssuanceFlow) { reservesString, totalIssuanceString -> - (reservesString + totalIssuanceString).take(5) - } - - emitAll(resultFlow) - } - // changes are in format [[storage key, value], [..], ..] class SubscribeStorageResult(val block: String, val changes: List>) { fun getSingleChange() = changes.first()[1] @@ -616,10 +537,7 @@ class PoolsRepositoryImpl @Inject constructor( ) } - private suspend fun getUserPoolsTokenIds( - chainId: ChainId, - address: String - ): List>> { + private suspend fun getUserPoolsTokenIds(chainId: ChainId, address: String): List>> { val storageKeys = getUserPoolsTokenIdsKeys(chainId, address) return storageKeys.map { storageKey -> chainRegistry.awaitConnection(chainId).socketService.executeAsync( @@ -889,15 +807,11 @@ class PoolsRepositoryImpl @Inject constructor( } } - fun RuntimeSnapshot.accountPoolsKey(address: String): String = - this.metadata.module(Modules.POOL_XYK) - .storage("AccountPools") - .storageKey(this, address.toAccountId()) + fun RuntimeSnapshot.accountPoolsKey(address: String): String = this.metadata.module(Modules.POOL_XYK) + .storage("AccountPools") + .storageKey(this, address.toAccountId()) - private fun mapPoolLocalToData( - poolLocal: UserPoolJoinedLocalNullable, - assets: List - ): CommonPoolData? { + private fun mapPoolLocalToData(poolLocal: UserPoolJoinedLocalNullable, assets: List): CommonPoolData? { val baseToken = assets.firstOrNull { it.currencyId == poolLocal.basicPoolLocal.tokenIdBase } ?: return null diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt index 68ca0cabbd..4a5809d440 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/data/network/Extrinsic.kt @@ -63,8 +63,7 @@ fun ExtrinsicBuilder.removeLiquidity( markerAssetDesired: BigInteger, outputAMin: BigInteger, outputBMin: BigInteger -) = - this.call( +) = this.call( Modules.POOL_XYK, "withdraw_liquidity", mapOf( @@ -77,6 +76,7 @@ fun ExtrinsicBuilder.removeLiquidity( ) ) +@Suppress("NestedBlockDepth", "LongParameterList") fun ExtrinsicBuilder.liquidityAdd( dexId: Int, baseTokenId: String?, diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt index c6edf66a27..063e4ae1a7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/di/PoolsModule.kt @@ -36,8 +36,7 @@ class PoolsModule { poolsRepository: PoolsRepository, accountRepository: AccountRepository, blockExplorerManager: BlockExplorerManager - ): PoolsInteractor = - PoolsInteractorImpl(poolsRepository, accountRepository, blockExplorerManager) + ): PoolsInteractor = PoolsInteractorImpl(poolsRepository, accountRepository, blockExplorerManager) @Provides @Singleton @@ -51,9 +50,7 @@ class PoolsModule { @Provides @Singleton - fun provideDemeterFarmingInteractor( - demeterFarmingRepository: DemeterFarmingRepository, - ): DemeterFarmingInteractor = + fun provideDemeterFarmingInteractor(demeterFarmingRepository: DemeterFarmingRepository): DemeterFarmingInteractor = DemeterFarmingInteractorImpl(demeterFarmingRepository) @Provides @@ -64,14 +61,13 @@ class PoolsModule { accountRepository: AccountRepository, walletRepository: WalletRepository, poolsRepository: PoolsRepository, - ): DemeterFarmingRepository = - DemeterFarmingRepositoryImpl( - chainRegistry, - bulkRetriever, - accountRepository, - walletRepository, - poolsRepository - ) + ): DemeterFarmingRepository = DemeterFarmingRepositoryImpl( + chainRegistry, + bulkRetriever, + accountRepository, + walletRepository, + poolsRepository + ) @Provides @Singleton diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt index 7aa46ce7f2..a8114d35e9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/domain/PoolsInteractorImpl.kt @@ -36,6 +36,13 @@ class PoolsInteractorImpl( override val poolsChainId = poolsRepository.poolsChainId + private val soraPoolsAddressFlow = flowOf { + val meta = accountRepository.getSelectedMetaAccount() + val chain = accountRepository.getChain(poolsChainId) + meta.address(chain) + }.mapNotNull { it } + .distinctUntilChanged() + override suspend fun getBasicPools(): List { return poolsRepository.getBasicPools(poolsChainId) } @@ -44,13 +51,6 @@ class PoolsInteractorImpl( return poolsRepository.subscribePools(address).flowOn(coroutineContext) } - private val soraPoolsAddressFlow = flowOf { - val meta = accountRepository.getSelectedMetaAccount() - val chain = accountRepository.getChain(poolsChainId) - meta.address(chain) - }.mapNotNull { it } - .distinctUntilChanged() - @OptIn(ExperimentalCoroutinesApi::class) override fun subscribePoolsCacheCurrentAccount(): Flow> { return soraPoolsAddressFlow.flatMapLatest { address -> @@ -96,10 +96,7 @@ class PoolsInteractorImpl( ) } - override suspend fun calcRemoveLiquidityNetworkFee( - tokenBase: Asset, - tokenTarget: Asset, - ): BigDecimal? { + override suspend fun calcRemoveLiquidityNetworkFee(tokenBase: Asset, tokenTarget: Asset): BigDecimal? { return poolsRepository.calcRemoveLiquidityNetworkFee( poolsChainId, tokenBase, @@ -118,13 +115,13 @@ class PoolsInteractorImpl( } override suspend fun observeRemoveLiquidity( - chainId: ChainId, - tokenBase: Asset, - tokenTarget: Asset, - markerAssetDesired: BigDecimal, - firstAmountMin: BigDecimal, - secondAmountMin: BigDecimal, - networkFee: BigDecimal + chainId: ChainId, + tokenBase: Asset, + tokenTarget: Asset, + markerAssetDesired: BigDecimal, + firstAmountMin: BigDecimal, + secondAmountMin: BigDecimal, + networkFee: BigDecimal ): String { val status = poolsRepository.observeRemoveLiquidity( chainId, @@ -174,6 +171,7 @@ class PoolsInteractorImpl( return status?.getOrNull() ?: "" } + @Suppress("OptionalUnit") override suspend fun syncPools(): Unit = withContext(Dispatchers.Default) { val address = accountRepository.getSelectedAccount(poolsChainId).address supervisorScope { @@ -183,6 +181,7 @@ class PoolsInteractorImpl( } } + @Suppress("OptionalUnit") override suspend fun updateAccountPools(): Unit = withContext(Dispatchers.Default) { val address = accountRepository.getSelectedAccount(poolsChainId).address poolsRepository.updateAccountPools(poolsChainId, address) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt index 890b96e54c..a84bcb4c09 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/navigation/InternalPoolsRouterImpl.kt @@ -58,7 +58,12 @@ class InternalPoolsRouterImpl( mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddScreen(ids)) } - override fun openAddLiquidityConfirmScreen(ids: StringPair, amountBase: BigDecimal, amountTarget: BigDecimal, apy: String) { + override fun openAddLiquidityConfirmScreen( + ids: StringPair, + amountBase: BigDecimal, + amountTarget: BigDecimal, + apy: String + ) { mutableRoutesFlow.tryEmit(LiquidityPoolsNavGraphRoute.LiquidityAddConfirmScreen(ids, amountBase, amountTarget, apy)) } @@ -100,7 +105,11 @@ class InternalPoolsRouterImpl( } } - override fun openSuccessScreen(txHash: String, chainId: ChainId, customMessage: String) { + override fun openSuccessScreen( + txHash: String, + chainId: ChainId, + customMessage: String + ) { walletRouter.openOperationSuccess(txHash, chainId, customMessage) } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt index b2ba2af845..3ad7a11005 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/PoolsFlowViewModel.kt @@ -10,7 +10,6 @@ import jp.co.soramitsu.common.presentation.LoadingState import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.feature_liquiditypools_impl.R -import jp.co.soramitsu.liquiditypools.domain.interfaces.PoolsInteractor import jp.co.soramitsu.liquiditypools.domain.model.CommonPoolData import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsPresenter import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.AllPoolsScreenInterface @@ -58,7 +57,6 @@ class PoolsFlowViewModel @Inject constructor( liquidityRemovePresenter: LiquidityRemovePresenter, liquidityRemoveConfirmPresenter: LiquidityRemoveConfirmPresenter, private val coroutinesStore: CoroutinesStore, - private val poolsInteractor: PoolsInteractor, private val internalPoolsRouter: InternalPoolsRouter, private val poolsRouter: LiquidityPoolsRouter ) : BaseViewModel(), @@ -70,11 +68,6 @@ class PoolsFlowViewModel @Inject constructor( LiquidityRemoveCallbacks by liquidityRemovePresenter, LiquidityRemoveConfirmCallbacks by liquidityRemoveConfirmPresenter { - companion object { - const val ITEM_APY_ID = 1 - const val ITEM_FEE_ID = 2 - } - val allPoolsScreenState: StateFlow = allPoolsPresenter.createScreenStateFlow(coroutinesStore.uiScope) @@ -181,6 +174,11 @@ class PoolsFlowViewModel @Inject constructor( fun exitFlow() { poolsRouter.back() } + + companion object { + const val ITEM_APY_ID = 1 + const val ITEM_FEE_ID = 2 + } } fun CommonPoolData.toListItemState(baseToken: Token?): BasicPoolListItemState? { diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt index 3a74e863a5..92fa6094f4 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/AllPoolsScreen.kt @@ -63,10 +63,7 @@ interface AllPoolsScreenInterface { } @Composable -fun AllPoolsScreenWithRefresh( - state: AllPoolsState, - callback: AllPoolsScreenInterface -) { +fun AllPoolsScreenWithRefresh(state: AllPoolsState, callback: AllPoolsScreenInterface) { PullRefreshBox( onRefresh = callback::onRefresh ) { @@ -75,10 +72,7 @@ fun AllPoolsScreenWithRefresh( } @Composable -fun AllPoolsScreen( - state: AllPoolsState, - callback: AllPoolsScreenInterface -) { +fun AllPoolsScreen(state: AllPoolsState, callback: AllPoolsScreenInterface) { val localDensity = LocalDensity.current var heightIs by remember { mutableStateOf(0.dp) @@ -100,7 +94,7 @@ fun AllPoolsScreen( ) { MarginVertical(margin = 8.dp) - if (state.isLoading || (state.userPools.isEmpty() && state.allPools.isEmpty())) { + if (state.isLoading || state.userPools.isEmpty() && state.allPools.isEmpty()) { BackgroundCorneredWithBorder( modifier = Modifier .padding(horizontal = 16.dp) @@ -182,7 +176,11 @@ fun ShimmerPoolList(size: Int = 10) { } @Composable -private fun PoolGroupHeader(modifier: Modifier = Modifier, title: String, onMoreClick: (() -> Unit)?) { +private fun PoolGroupHeader( + modifier: Modifier = Modifier, + title: String, + onMoreClick: (() -> Unit)? +) { Box(modifier = modifier.wrapContentHeight()) { Row( modifier = Modifier diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt index 96bd9e436e..c79e45d444 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/allpools/BasicPoolListItem.kt @@ -182,9 +182,7 @@ fun BasicPoolListItem( } @Composable -fun BasicPoolShimmerItem( - modifier: Modifier = Modifier, -) { +fun BasicPoolShimmerItem(modifier: Modifier = Modifier) { Row( modifier = modifier .fillMaxWidth() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt index f98f1ba79c..51901e5fda 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddPresenter.kt @@ -62,6 +62,7 @@ import javax.inject.Inject import kotlin.math.min import jp.co.soramitsu.core.models.Asset as CoreAsset +@Suppress("LargeClass") class LiquidityAddPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, private val internalPoolsRouter: InternalPoolsRouter, @@ -102,21 +103,67 @@ class LiquidityAddPresenter @Inject constructor( } .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - private fun areArgsEquivalent(): ( - old: LiquidityPoolsNavGraphRoute.LiquidityAddScreen, - new: LiquidityPoolsNavGraphRoute.LiquidityAddScreen - ) -> Boolean = { old, new -> - old.routeName == new.routeName && - old.ids.first == new.ids.first && - old.ids.second == new.ids.second - } - private val loadingAssetsInPoolFlow = MutableStateFlow>>(LoadingState.Loading()) private val tokensInPoolFlow = loadingAssetsInPoolFlow.filterIsInstance>>().map { it.data.first.token.configuration to it.data.second.token.configuration }.distinctUntilChanged() + val isPoolPairEnabled = + screenArgsFlow.map { screenArgs -> + val (baseTokenId, targetTokenId) = screenArgs.ids + poolsInteractor.isPairEnabled( + baseTokenId, + targetTokenId + ) + } + + val networkFeeFlow = combine( + enteredBaseAmountFlow, + enteredTargetAmountFlow, + tokensInPoolFlow, + stateSlippage, + isPoolPairEnabled + ) { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> + getLiquidityNetworkFee( + tokenBase = baseAsset, + tokenTarget = targetAsset, + tokenBaseAmount = amountBase, + tokenToAmount = amountTarget, + pairEnabled = pairEnabled, + pairPresented = true, + slippageTolerance = slippage + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + val poolFlow = screenArgsFlow.flatMapLatest { screenargs -> + poolsInteractor.getPoolData( + baseTokenId = screenargs.ids.first, + targetTokenId = screenargs.ids.second + ) + } + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) private fun subscribeState(coroutineScope: CoroutineScope) { screenArgsFlow.onEach { @@ -150,7 +197,7 @@ class LiquidityAddPresenter @Inject constructor( desired = WithDesired.INPUT isCalculatingAmounts.value = desired } - .debounce(900) + .debounce(INPUT_DEBOUNCE) .onEach { amount -> amountBase = amount updateAmounts() @@ -161,21 +208,13 @@ class LiquidityAddPresenter @Inject constructor( desired = WithDesired.OUTPUT isCalculatingAmounts.value = desired } - .debounce(900) + .debounce(INPUT_DEBOUNCE) .onEach { amount -> amountTarget = amount updateAmounts() }.launchIn(coroutineScope) } - @OptIn(ExperimentalCoroutinesApi::class) - val poolFlow = screenArgsFlow.flatMapLatest { screenargs -> - poolsInteractor.getPoolData( - baseTokenId = screenargs.ids.first, - targetTokenId = screenargs.ids.second - ) - } - @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { subscribeState(coroutineScope) @@ -185,7 +224,7 @@ class LiquidityAddPresenter @Inject constructor( amountBase = BigDecimal.ZERO uiBaseAmountFlow.value = BigDecimal.ZERO uiTargetAmountFlow.value = BigDecimal.ZERO - }.debounce(200).flatMapLatest { + }.debounce(RESET_DEBOUNCE).flatMapLatest { combine( poolFlow, loadingAssetsInPoolFlow, @@ -259,9 +298,10 @@ class LiquidityAddPresenter @Inject constructor( private suspend fun updateAmounts() { calculateAmount()?.let { targetAmount -> - val scaledTargetAmount = when { - targetAmount.isZero() -> BigDecimal.ZERO - else -> targetAmount.setScale( + val scaledTargetAmount = if (targetAmount.isZero()) { + BigDecimal.ZERO + } else { + targetAmount.setScale( min(MAX_DECIMALS_8, targetAmount.scale()), RoundingMode.DOWN ) @@ -314,53 +354,6 @@ class LiquidityAddPresenter @Inject constructor( } } - val isPoolPairEnabled = - screenArgsFlow.map { screenArgs -> - val (baseTokenId, targetTokenId) = screenArgs.ids - poolsInteractor.isPairEnabled( - baseTokenId, - targetTokenId - ) - } - - val networkFeeFlow = combine( - enteredBaseAmountFlow, - enteredTargetAmountFlow, - tokensInPoolFlow, - stateSlippage, - isPoolPairEnabled - ) { amountBase, amountTarget, (baseAsset, targetAsset), slippage, pairEnabled -> - getLiquidityNetworkFee( - tokenBase = baseAsset, - tokenTarget = targetAsset, - tokenBaseAmount = amountBase, - tokenToAmount = amountTarget, - pairEnabled = pairEnabled, - pairPresented = true, - slippageTolerance = slippage - ) - } - - @OptIn(ExperimentalCoroutinesApi::class) - private val feeInfoViewStateFlow: Flow = - flowOf { - requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> - combine( - networkFeeFlow, - walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) - ) { networkFee, utilityAsset -> - val tokenSymbol = utilityAsset.token.configuration.symbol - val tokenFiatRate = utilityAsset.token.fiatRate - val tokenFiatSymbol = utilityAsset.token.fiatSymbol - - FeeInfoViewState( - feeAmount = networkFee.formatCryptoDetail(tokenSymbol), - feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), - ) - } - } - private suspend fun getLiquidityNetworkFee( tokenBase: CoreAsset, tokenTarget: CoreAsset, @@ -432,7 +425,7 @@ class LiquidityAddPresenter @Inject constructor( internalPoolsRouter.openAddLiquidityConfirmScreen(ids, amountBase, amountTarget, apy) }.invokeOnCompletion { coroutinesStore.uiScope.launch { - delay(300) + delay(DEBOUNCE_300) setButtonLoading(false) } } @@ -469,15 +462,27 @@ class LiquidityAddPresenter @Inject constructor( } private fun showError(throwable: Throwable) { - when (throwable) { - is ValidationException -> { - val (title, text) = throwable - internalPoolsRouter.openErrorsScreen(title, text) - } + if (throwable is ValidationException) { + val (title, text) = throwable + internalPoolsRouter.openErrorsScreen(title, text) + } else { + throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } + } + } - else -> { - throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } - } + private fun areArgsEquivalent(): ( + old: LiquidityPoolsNavGraphRoute.LiquidityAddScreen, + new: LiquidityPoolsNavGraphRoute.LiquidityAddScreen + ) -> Boolean = + { old, new -> + old.routeName == new.routeName && + old.ids.first == new.ids.first && + old.ids.second == new.ids.second } + + companion object { + private const val INPUT_DEBOUNCE = 900L + private const val RESET_DEBOUNCE = 200L + private const val DEBOUNCE_300 = 300L } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt index 96144f4651..ebff1611c0 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityadd/LiquidityAddScreen.kt @@ -67,10 +67,7 @@ interface LiquidityAddCallbacks { } @Composable -fun LiquidityAddScreen( - state: LiquidityAddState, - callbacks: LiquidityAddCallbacks -) { +fun LiquidityAddScreen(state: LiquidityAddState, callbacks: LiquidityAddCallbacks) { val keyboardController = LocalSoftwareKeyboardController.current val runCallback: (() -> Unit) -> Unit = { block -> keyboardController?.hide() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt index 895353f541..1712f31d2d 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmPresenter.kt @@ -91,38 +91,6 @@ class LiquidityAddConfirmPresenter @Inject constructor( ) } - private val stateFlow = MutableStateFlow(LiquidityAddConfirmState()) - - fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { - subscribeState(coroutineScope) - return stateFlow - } - - private fun subscribeState(coroutineScope: CoroutineScope) { - combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> - stateFlow.value = stateFlow.value.copy( - assetBase = assetBase.configuration, - assetTarget = assetTarget.configuration, - baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), - targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), - apy = screenArgs.apy - ) - }.launchIn(coroutineScope) - - stateSlippage.onEach { - stateFlow.value = stateFlow.value.copy( - slippage = "$it%" - ) - }.launchIn(coroutineScope) - - feeInfoViewStateFlow.onEach { - stateFlow.value = stateFlow.value.copy( - feeInfo = it, - buttonEnabled = it.feeAmount.isNullOrEmpty().not() - ) - }.launchIn(coroutineScope) - } - val networkFeeFlow = combine( screenArgsFlow, tokensInPoolFlow, @@ -160,6 +128,38 @@ class LiquidityAddConfirmPresenter @Inject constructor( } } + private val stateFlow = MutableStateFlow(LiquidityAddConfirmState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> + stateFlow.value = stateFlow.value.copy( + assetBase = assetBase.configuration, + assetTarget = assetTarget.configuration, + baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), + targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), + apy = screenArgs.apy + ) + }.launchIn(coroutineScope) + + stateSlippage.onEach { + stateFlow.value = stateFlow.value.copy( + slippage = "$it%" + ) + }.launchIn(coroutineScope) + + feeInfoViewStateFlow.onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it, + buttonEnabled = it.feeAmount.isNullOrEmpty().not() + ) + }.launchIn(coroutineScope) + } + private suspend fun getLiquidityNetworkFee( tokenBase: Asset, tokenTarget: Asset, @@ -224,12 +224,12 @@ class LiquidityAddConfirmPresenter @Inject constructor( } }.invokeOnCompletion { coroutinesStore.ioScope.launch { - delay(700) + delay(UPDATE_POOL_DELAY) poolsInteractor.updateAccountPools() } coroutinesStore.uiScope.launch { - delay(300) + delay(DEBOUNCE_300) setButtonLoading(false) } } @@ -244,4 +244,9 @@ class LiquidityAddConfirmPresenter @Inject constructor( buttonLoading = loading ) } + + companion object { + private const val UPDATE_POOL_DELAY = 700L + private const val DEBOUNCE_300 = 300L + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt index 1b0684a9d4..8816e05d53 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityaddconfirm/LiquidityAddConfirmScreen.kt @@ -67,10 +67,7 @@ interface LiquidityAddConfirmCallbacks { } @Composable -fun LiquidityAddConfirmScreen( - state: LiquidityAddConfirmState, - callbacks: LiquidityAddConfirmCallbacks -) { +fun LiquidityAddConfirmScreen(state: LiquidityAddConfirmState, callbacks: LiquidityAddConfirmCallbacks) { Column( modifier = Modifier .background(backgroundBlack) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index bab14c5ff7..2cabbd3021 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -58,6 +58,7 @@ import javax.inject.Inject import kotlin.math.min @OptIn(FlowPreview::class) +@Suppress("LargeClass") class LiquidityRemovePresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, private val internalPoolsRouter: InternalPoolsRouter, @@ -89,21 +90,6 @@ class LiquidityRemovePresenter @Inject constructor( } .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) - private fun resetState() { - amountTarget = BigDecimal.ZERO - amountBase = BigDecimal.ZERO - stateFlow.value = stateFlow.value.copy( - baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( - tokenAmount = BigDecimal.ZERO, - fiatAmount = null - ), - targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( - tokenAmount = BigDecimal.ZERO, - fiatAmount = null - ) - ) - } - private val baseToTargetTokensFlow = screenArgsFlow.mapNotNull { screenArgs -> val currencyIds = screenArgs.ids val chainId = poolsInteractor.poolsChainId @@ -128,6 +114,42 @@ class LiquidityRemovePresenter @Inject constructor( ) } + private val networkFeeFlow = baseToTargetTokensFlow.map { (baseToken, targetToken) -> + getRemoveLiquidityNetworkFee( + tokenBase = baseToken.configuration, + tokenTarget = targetToken.configuration, + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + val utilityAssetFlow = flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private val feeInfoViewStateFlow: Flow = + flowOf { + requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) + }.flatMapLatest { utilityAssetId -> + combine( + networkFeeFlow, + walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) + ) { networkFee, utilityAsset -> + val tokenSymbol = utilityAsset.token.configuration.symbol + val tokenFiatRate = utilityAsset.token.fiatRate + val tokenFiatSymbol = utilityAsset.token.fiatSymbol + + FeeInfoViewState( + feeAmount = networkFee.formatCryptoDetail(tokenSymbol), + feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), + ) + } + } + + private val stateFlow = MutableStateFlow(LiquidityRemoveState()) + init { coroutinesStore.ioScope.launch { poolDataFlow.map { data -> @@ -140,7 +162,7 @@ class LiquidityRemovePresenter @Inject constructor( } .catch { showError(it) } .distinctUntilChanged() - .debounce(500) + .debounce(DEBOUNCE_500) .map { poolDataLocal -> poolDataReal = poolDataLocal poolInFarming = false @@ -220,6 +242,21 @@ class LiquidityRemovePresenter @Inject constructor( } } + private fun resetState() { + amountTarget = BigDecimal.ZERO + amountBase = BigDecimal.ZERO + stateFlow.value = stateFlow.value.copy( + baseAmountInputViewState = stateFlow.value.baseAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ), + targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( + tokenAmount = BigDecimal.ZERO, + fiatAmount = null + ) + ) + } + @OptIn(FlowPreview::class) private fun subscribeState(coroutineScope: CoroutineScope) { poolDataFlow.onEach { pool -> @@ -266,7 +303,7 @@ class LiquidityRemovePresenter @Inject constructor( ) ) } - .debounce(900) + .debounce(INPUT_DEBOUNCE) .onEach { amount -> poolDataUsable?.let { amountBase = if (it.user.basePooled <= amount) amount else it.user.basePooled @@ -299,7 +336,7 @@ class LiquidityRemovePresenter @Inject constructor( ), ) } - .debounce(900) + .debounce(INPUT_DEBOUNCE) .onEach { amount -> poolDataUsable?.let { amountTarget = if (amount <= it.user.targetPooled) amount else it.user.targetPooled @@ -346,19 +383,19 @@ class LiquidityRemovePresenter @Inject constructor( }.launchIn(coroutineScope) } - private val stateFlow = MutableStateFlow(LiquidityRemoveState()) - fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { subscribeState(coroutineScope) return stateFlow } + @Suppress("NestedBlockDepth") private suspend fun updateAmounts() { baseToTargetTokensFlow.firstOrNull()?.let { (tokenBase, tokenTarget) -> if (amountBase.compareTo(stateFlow.value.baseAmountInputViewState.tokenAmount) != 0) { - val scaledAmountBase = when { - amountBase.isZero() -> BigDecimal.ZERO - else -> amountBase.setScale( + val scaledAmountBase = if (amountBase.isZero()) { + BigDecimal.ZERO + } else { + amountBase.setScale( min(MAX_DECIMALS_8, amountBase.scale()), RoundingMode.DOWN ) @@ -372,9 +409,10 @@ class LiquidityRemovePresenter @Inject constructor( ) } if (amountTarget.compareTo(stateFlow.value.targetAmountInputViewState.tokenAmount) != 0) { - val scaledAmountTarget = when { - amountTarget.isZero() -> BigDecimal.ZERO - else -> amountTarget.setScale( + val scaledAmountTarget = if (amountTarget.isZero()) { + BigDecimal.ZERO + } else { + amountTarget.setScale( min(MAX_DECIMALS_8, amountTarget.scale()), RoundingMode.DOWN ) @@ -398,44 +436,7 @@ class LiquidityRemovePresenter @Inject constructor( ) } - private val networkFeeFlow = baseToTargetTokensFlow.map { (baseToken, targetToken) -> - getRemoveLiquidityNetworkFee( - tokenBase = baseToken.configuration, - tokenTarget = targetToken.configuration, - ) - } - - @OptIn(ExperimentalCoroutinesApi::class) - val utilityAssetFlow = flowOf { - requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> - walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) - } - - @OptIn(ExperimentalCoroutinesApi::class) - private val feeInfoViewStateFlow: Flow = - flowOf { - requireNotNull(chainsRepository.getChain(poolsInteractor.poolsChainId).utilityAsset?.id) - }.flatMapLatest { utilityAssetId -> - combine( - networkFeeFlow, - walletInteractor.assetFlow(poolsInteractor.poolsChainId, utilityAssetId) - ) { networkFee, utilityAsset -> - val tokenSymbol = utilityAsset.token.configuration.symbol - val tokenFiatRate = utilityAsset.token.fiatRate - val tokenFiatSymbol = utilityAsset.token.fiatSymbol - - FeeInfoViewState( - feeAmount = networkFee.formatCryptoDetail(tokenSymbol), - feeAmountFiat = networkFee.applyFiatRate(tokenFiatRate)?.formatFiat(tokenFiatSymbol), - ) - } - } - - private suspend fun getRemoveLiquidityNetworkFee( - tokenBase: Asset, - tokenTarget: Asset, - ): BigDecimal { + private suspend fun getRemoveLiquidityNetworkFee(tokenBase: Asset, tokenTarget: Asset): BigDecimal { val result = poolsInteractor.calcRemoveLiquidityNetworkFee( tokenBase, tokenTarget, @@ -481,7 +482,7 @@ class LiquidityRemovePresenter @Inject constructor( return@launch } - val slippage = 0.5 + val slippage = DEFAULT_SLIPPAGE val firstAmountMin = PolkaswapFormulas.calculateMinAmount( @@ -509,7 +510,7 @@ class LiquidityRemovePresenter @Inject constructor( internalPoolsRouter.openRemoveLiquidityConfirmScreen(ids, amountBase, amountTarget, firstAmountMin, secondAmountMin, desired) }.invokeOnCompletion { coroutinesStore.uiScope.launch { - delay(300) + delay(DEBOUNCE_300) setButtonLoading(false) } } @@ -538,15 +539,18 @@ class LiquidityRemovePresenter @Inject constructor( } private fun showError(throwable: Throwable) { - when (throwable) { - is ValidationException -> { - val (title, text) = throwable - internalPoolsRouter.openErrorsScreen(title, text) - } - - else -> { - throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } - } + if (throwable is ValidationException) { + val (title, text) = throwable + internalPoolsRouter.openErrorsScreen(title, text) + } else { + throwable.message?.let { internalPoolsRouter.openErrorsScreen(message = it) } } } + + companion object { + private const val INPUT_DEBOUNCE = 900L + private const val DEBOUNCE_300 = 300L + private const val DEBOUNCE_500 = 500L + private const val DEFAULT_SLIPPAGE = 0.5 + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt index 34fd485658..af8c86785b 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemoveScreen.kt @@ -67,10 +67,7 @@ interface LiquidityRemoveCallbacks { } @Composable -fun LiquidityRemoveScreen( - state: LiquidityRemoveState, - callbacks: LiquidityRemoveCallbacks -) { +fun LiquidityRemoveScreen(state: LiquidityRemoveState, callbacks: LiquidityRemoveCallbacks) { val keyboardController = LocalSoftwareKeyboardController.current val runCallback: (() -> Unit) -> Unit = { block -> keyboardController?.hide() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 2c3fb53a7a..4d062bc651 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -90,33 +90,6 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( ) } - private val stateFlow = MutableStateFlow(LiquidityRemoveConfirmState()) - - fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { - subscribeState(coroutineScope) - return stateFlow - } - - private fun subscribeState(coroutineScope: CoroutineScope) { - combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> - stateFlow.value = stateFlow.value.copy( - assetBaseIconUrl = assetBase.configuration.iconUrl, - assetTargetIconUrl = assetTarget.configuration.iconUrl, - baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), - baseFiat = screenArgs.amountBase.applyFiatRate(assetBase.fiatRate)?.formatFiat(assetBase.fiatSymbol).orEmpty(), - targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), - targetFiat = screenArgs.amountTarget.applyFiatRate(assetTarget.fiatRate)?.formatFiat(assetTarget.fiatSymbol).orEmpty(), - ) - }.launchIn(coroutineScope) - - feeInfoViewStateFlow.onEach { - stateFlow.value = stateFlow.value.copy( - feeInfo = it, - buttonEnabled = it.feeAmount.isNullOrEmpty().not() - ) - }.launchIn(coroutineScope) - } - private val networkFeeFlow = combine( screenArgsFlow, tokensInPoolFlow, @@ -154,6 +127,33 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } } + private val stateFlow = MutableStateFlow(LiquidityRemoveConfirmState()) + + fun createScreenStateFlow(coroutineScope: CoroutineScope): StateFlow { + subscribeState(coroutineScope) + return stateFlow + } + + private fun subscribeState(coroutineScope: CoroutineScope) { + combine(screenArgsFlow, tokensInPoolFlow) { screenArgs, (assetBase, assetTarget) -> + stateFlow.value = stateFlow.value.copy( + assetBaseIconUrl = assetBase.configuration.iconUrl, + assetTargetIconUrl = assetTarget.configuration.iconUrl, + baseAmount = screenArgs.amountBase.formatCrypto(assetBase.configuration.symbol), + baseFiat = screenArgs.amountBase.applyFiatRate(assetBase.fiatRate)?.formatFiat(assetBase.fiatSymbol).orEmpty(), + targetAmount = screenArgs.amountTarget.formatCrypto(assetTarget.configuration.symbol), + targetFiat = screenArgs.amountTarget.applyFiatRate(assetTarget.fiatRate)?.formatFiat(assetTarget.fiatSymbol).orEmpty(), + ) + }.launchIn(coroutineScope) + + feeInfoViewStateFlow.onEach { + stateFlow.value = stateFlow.value.copy( + feeInfo = it, + buttonEnabled = it.feeAmount.isNullOrEmpty().not() + ) + }.launchIn(coroutineScope) + } + private suspend fun getLiquidityNetworkFee( tokenBase: Asset, tokenTarget: Asset, @@ -221,12 +221,12 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( } }.invokeOnCompletion { coroutinesStore.ioScope.launch { - delay(700) + delay(UPDATE_POOL_DELAY) poolsInteractor.updateAccountPools() } coroutinesStore.uiScope.launch { - delay(300) + delay(DEBOUNCE_300) setButtonLoading(false) } } @@ -241,4 +241,9 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( buttonLoading = loading ) } + + companion object { + private const val UPDATE_POOL_DELAY = 700L + private const val DEBOUNCE_300 = 300L + } } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt index fac72a3ec0..ac691d77c9 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmScreen.kt @@ -60,10 +60,7 @@ interface LiquidityRemoveConfirmCallbacks { } @Composable -fun LiquidityRemoveConfirmScreen( - state: LiquidityRemoveConfirmState, - callbacks: LiquidityRemoveConfirmCallbacks -) { +fun LiquidityRemoveConfirmScreen(state: LiquidityRemoveConfirmState, callbacks: LiquidityRemoveConfirmCallbacks) { Column( modifier = Modifier .background(backgroundBlack) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt index 465af9c2d2..21d4816e74 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/pooldetails/PoolDetailsScreen.kt @@ -61,10 +61,7 @@ interface PoolDetailsCallbacks { } @Composable -fun PoolDetailsScreen( - state: PoolDetailsState, - callbacks: PoolDetailsCallbacks -) { +fun PoolDetailsScreen(state: PoolDetailsState, callbacks: PoolDetailsCallbacks) { Column( modifier = Modifier .fillMaxSize() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 40a6e60d91..5d305e13d2 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -26,6 +26,8 @@ import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.BasicPoolListIt import jp.co.soramitsu.liquiditypools.impl.presentation.allpools.ShimmerPoolList import jp.co.soramitsu.ui_core.resources.Dimens +private const val SHIMMERS_SIZE = 20 + data class PoolListState( val pools: List = listOf(), val searchQuery: String? = null, @@ -38,10 +40,7 @@ interface PoolListScreenInterface { } @Composable -fun PoolListScreen( - state: PoolListState, - callback: PoolListScreenInterface -) { +fun PoolListScreen(state: PoolListState, callback: PoolListScreenInterface) { Column( modifier = Modifier.fillMaxSize(), ) { @@ -57,8 +56,9 @@ fun PoolListScreen( onInput = callback::onAssetSearchEntered ) } + if (state.isLoading) { - ShimmerPoolList(20) + ShimmerPoolList(SHIMMERS_SIZE) } else { val listState = rememberLazyListState() diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt index 9ff712898a..6d41427b48 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateAddLiquidityUseCase.kt @@ -42,9 +42,7 @@ class ValidateAddLiquidityUseCase @Inject constructor() { } } - private fun performChecks( - checks: Map, - ): TransferValidationResult { + private fun performChecks(checks: Map): TransferValidationResult { checks.forEach { (result, condition) -> if (condition) return result } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt index c193baa171..2d0e62a65a 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/usecase/ValidateRemoveLiquidityUseCase.kt @@ -31,9 +31,7 @@ class ValidateRemoveLiquidityUseCase @Inject constructor() { } } - private fun performChecks( - checks: Map, - ): TransferValidationResult { + private fun performChecks(checks: Map): TransferValidationResult { checks.forEach { (result, condition) -> if (condition) return result } diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt index 2d06fa93c1..f9a1d186d7 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/util/PolkaswapFormulas.kt @@ -17,20 +17,15 @@ object PolkaswapFormulas { ): BigDecimal = reserves.multiply(poolProvidersBalance).divideBy(totalIssuance, precision) - private fun calculateShareOfPool( - poolProvidersBalance: BigDecimal, - totalIssuance: BigDecimal - ): BigDecimal = + private fun calculateShareOfPool(poolProvidersBalance: BigDecimal, totalIssuance: BigDecimal): BigDecimal = poolProvidersBalance.divideBy(totalIssuance).multiply(Big100) - fun calculateShareOfPoolFromAmount( - amount: BigDecimal, - amountPooled: BigDecimal, - ): Double = if (amount.equalTo(amountPooled)) { - 100.0 - } else { - calculateShareOfPool(amount, amountPooled).toDouble() - } + fun calculateShareOfPoolFromAmount(amount: BigDecimal, amountPooled: BigDecimal): Double = + if (amount.equalTo(amountPooled)) { + 100.0 + } else { + calculateShareOfPool(amount, amountPooled).toDouble() + } fun calculateAddLiquidityAmount( baseAmount: BigDecimal, @@ -67,17 +62,11 @@ object PolkaswapFormulas { .multiply(Big100) .safeDivide(reserves.minus(amount)) - fun calculateMinAmount( - amount: BigDecimal, - slippageTolerance: Double, - ): BigDecimal { + fun calculateMinAmount(amount: BigDecimal, slippageTolerance: Double): BigDecimal { return amount.minus(amount.multiply(BigDecimal.valueOf(slippageTolerance / 100))) } - fun calculateTokenPerTokenRate( - amount1: BigDecimal, - amount2: BigDecimal, - ): BigDecimal { + fun calculateTokenPerTokenRate(amount1: BigDecimal, amount2: BigDecimal): BigDecimal { return amount1.safeDivide(amount2) } @@ -87,9 +76,7 @@ object PolkaswapFormulas { totalIssuance: BigDecimal, ): BigDecimal = fromAmount.safeDivide(firstReserves).multiply(totalIssuance) - fun calculateStrategicBonusAPY( - strategicBonusApy: Double? - ): Double? { + fun calculateStrategicBonusAPY(strategicBonusApy: Double?): Double? { return strategicBonusApy?.times(100) } From 55b8a4f603265a43dbbf698736e303eb1b74a81f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Tue, 20 Aug 2024 21:19:15 +0500 Subject: [PATCH 64/84] foundation update --- android-foundation | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-foundation b/android-foundation index cd23abbb1f..c7065d188d 160000 --- a/android-foundation +++ b/android-foundation @@ -1 +1 @@ -Subproject commit cd23abbb1f915bf64f2707b94af49e92e69969cf +Subproject commit c7065d188d4e7a95f66b505d0e8ca709dfff4fb0 From ffc34379f943047ee2dcbe65dd6acd919157d434 Mon Sep 17 00:00:00 2001 From: Deneath Date: Wed, 21 Aug 2024 11:56:01 +0700 Subject: [PATCH 65/84] fixed ethereum balances update Signed-off-by: Deneath --- .../data/network/blockchain/updaters/BalancesUpdateSystem.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt index c7429cbcae..1b84a95906 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt @@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -110,7 +111,7 @@ class BalancesUpdateSystem( chain: Chain, metaAccount: MetaAccount ): Flow> { - return trigger.map { triggeredChainId -> + return trigger.onStart { emit(null) }.map { triggeredChainId -> val specificChainTriggered = triggeredChainId != null val currentChainTriggered = triggeredChainId == chain.id From 2782e3e4fb51c6c9daef816d7aca456b995f819b Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 11:01:43 +0500 Subject: [PATCH 66/84] FLW-4569 There should be new "All done" screen --- common/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index d543efbde8..d58caa1169 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1092,7 +1092,7 @@ Substrate keypair crypto type Substrate secret derivation path\n You can return to your browser now - Your transaction has been successfully sent to blockchain + Your transaction has been successfully sent to the blockchain Support & Feedback Switch node Auto select nodes From 6269cfe853932a95aeaa98f48e01ee189b1b9ad9 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 11:04:45 +0500 Subject: [PATCH 67/84] version up 3.7.1(194) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b44dfefdcd..a231a056cb 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ apply plugin: "org.sonarqube" buildscript { ext { // App version - versionName = '3.6.2' - versionCode = 193 + versionName = '3.7.1' + versionCode = 194 // SDK and tools compileSdkVersion = 34 From 33522fc97979baccc7b39537864b52460787a674 Mon Sep 17 00:00:00 2001 From: Deneath Date: Wed, 21 Aug 2024 16:16:30 +0700 Subject: [PATCH 68/84] fixed score stars colors, fixed polkaswap disclaimer, fixed score button Signed-off-by: Deneath --- .../root/presentation/main/MainViewModel.kt | 29 +++++-------------- .../common/compose/component/ScoreStar.kt | 16 ++++++---- .../optionswallet/OptionsWalletContent.kt | 24 ++++++++------- .../optionswallet/OptionsWalletViewModel.kt | 5 ++-- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt index 3c01f15797..690547a203 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainViewModel.kt @@ -1,8 +1,6 @@ package jp.co.soramitsu.app.root.presentation.main -import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.app.root.domain.RootInteractor import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.core.runtime.ChainConnection @@ -10,8 +8,7 @@ import jp.co.soramitsu.polkaswap.api.domain.PolkaswapInteractor import jp.co.soramitsu.polkaswap.api.presentation.PolkaswapRouter import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( @@ -24,29 +21,19 @@ class MainViewModel @Inject constructor( init { externalRequirements.value = ChainConnection.ExternalRequirement.ALLOWED - walletRouter.listenPolkaswapDisclaimerResultFlowFromMainScreen() - .onEach { - if (it) { - walletRouter.openSwapTokensScreen( - chainId = null, - assetIdFrom = null, - assetIdTo = null - ) - } - }.launchIn(viewModelScope) } val stakingAvailableLiveData = interactor.stakingAvailableFlow() .asLiveData() fun navigateToSwapScreen() { - if (polkaswapInteractor.hasReadDisclaimer) { - walletRouter.openSwapTokensScreen( - chainId = null, - assetIdFrom = null, - assetIdTo = null - ) - } else { + walletRouter.openSwapTokensScreen( + chainId = null, + assetIdFrom = null, + assetIdTo = null + ) + + if (!polkaswapInteractor.hasReadDisclaimer) { polkaswapRouter.openPolkaswapDisclaimerFromMainScreen() } } diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt index 11f915c059..46226a6a37 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/ScoreStar.kt @@ -17,11 +17,17 @@ import jp.co.soramitsu.common.compose.theme.white30 @Composable fun ScoreStar(score: Int, modifier: Modifier = Modifier) { - val (starRes, color) = when (score) { - in 0..33 -> R.drawable.ic_score_star_empty to warningOrange - in 33..66 -> R.drawable.ic_score_star_half to warningYellow - in 66..100 -> R.drawable.ic_score_star_full to greenText - else -> R.drawable.ic_score_star_empty to white30 + val starRes = when (score) { + in 0..33 -> R.drawable.ic_score_star_empty + in 33..66 -> R.drawable.ic_score_star_half + in 66..100 -> R.drawable.ic_score_star_full + else -> R.drawable.ic_score_star_empty + } + val color = when (score) { + in 0..25 -> warningOrange + in 25..75 -> warningYellow + in 75..100 -> greenText + else -> white30 } when { diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt index 5495e50e82..60c98d6813 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletContent.kt @@ -28,7 +28,8 @@ import jp.co.soramitsu.common.compose.theme.grayButtonBackground import jp.co.soramitsu.feature_wallet_impl.R data class OptionsWalletScreenViewState( - val isSelected: Boolean + val isSelected: Boolean, + val showScoreButton: Boolean ) interface OptionsWalletCallback { @@ -106,14 +107,16 @@ fun OptionsWalletContent( text = stringResource(id = R.string.change_wallet_name), onClick = callback::onChangeWalletNameClick ) - MarginVertical(margin = 12.dp) - GrayButton( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), - text = stringResource(id = R.string.account_stats_wallet_option_title), - onClick = callback::onShowWalletScoreClick - ) + if (state.showScoreButton) { + MarginVertical(margin = 12.dp) + GrayButton( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + text = stringResource(id = R.string.account_stats_wallet_option_title), + onClick = callback::onShowWalletScoreClick + ) + } if (!state.isSelected) { MarginVertical(margin = 12.dp) TextButton( @@ -136,7 +139,8 @@ private fun OptionsWalletScreenPreview() { FearlessAppTheme() { OptionsWalletContent( state = OptionsWalletScreenViewState( - isSelected = false + isSelected = false, + showScoreButton = true ), callback = object : OptionsWalletCallback { override fun onChangeWalletNameClick() {} diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt index 73b8084ab1..da4347b5f3 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/optionswallet/OptionsWalletViewModel.kt @@ -39,11 +39,12 @@ class OptionsWalletViewModel @Inject constructor( }.stateIn(viewModelScope, SharingStarted.Eagerly, null) val state: StateFlow = selectedWallet.map { - OptionsWalletScreenViewState(it.id == walletId) + + OptionsWalletScreenViewState(it.id == walletId, it.ethereumAddress != null) }.stateIn( viewModelScope, SharingStarted.Eagerly, - OptionsWalletScreenViewState(true) + OptionsWalletScreenViewState(isSelected = true, showScoreButton = false) ) override fun onChangeWalletNameClick() { From d41e9f0e37c62b66280611cf6eec90ec88dd0942 Mon Sep 17 00:00:00 2001 From: Deneath Date: Wed, 21 Aug 2024 17:50:53 +0700 Subject: [PATCH 69/84] version up Signed-off-by: Deneath --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a231a056cb..6c41441d0c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 194 + versionCode = 195 // SDK and tools compileSdkVersion = 34 From c65ebab842c24e0dbd48860cd235e16fb85ec33f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 17:21:15 +0500 Subject: [PATCH 70/84] clean --- .../impl/data/repository/datasource/AccountDataSourceImpl.kt | 2 +- .../presentation/cross_chain/setup/CrossChainSetupContent.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/datasource/AccountDataSourceImpl.kt index eb4ca939da..86fcef9b6c 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -158,7 +158,7 @@ class AccountDataSourceImpl( override suspend fun getSelectedMetaAccount(): MetaAccount { val chainsById = chainsRepository.getChainsById() val selectedMetaAccount = metaAccountDao.selectedMetaAccountInfo() - return mapMetaAccountLocalToMetaAccount(chainsById,selectedMetaAccount) + return mapMetaAccountLocalToMetaAccount(chainsById, selectedMetaAccount) } override fun selectedMetaAccountFlow(): Flow = selectedMetaAccountFlow diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/cross_chain/setup/CrossChainSetupContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/cross_chain/setup/CrossChainSetupContent.kt index 6659ca112a..f5ef5a44e1 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/cross_chain/setup/CrossChainSetupContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/cross_chain/setup/CrossChainSetupContent.kt @@ -77,7 +77,6 @@ interface CrossChainSetupScreenInterface { fun onWarningInfoClick() } -@OptIn(ExperimentalComposeUiApi::class) @Composable fun CrossChainSetupContent( state: CrossChainSetupViewState, From 998870eef4d4702316d0f84e748daccdc8ac4c61 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 18:25:21 +0500 Subject: [PATCH 71/84] migration fix --- .../soramitsu/coredb/migrations/Migrations.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt index 06caa35398..3874dcad75 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt @@ -19,12 +19,37 @@ val Migration_68_69 = object : Migration(68, 69) { `avgTransactionTimeInHours` REAL NOT NULL, `maxTransactionTimeInHours` REAL NOT NULL, `minTransactionTimeInHours` REAL NOT NULL, - `scoredAt` STRING NOT NULL, + `scoredAt` TEXT NOT NULL, PRIMARY KEY(`metaId`), FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) """.trimIndent() ) + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `allpools` ( + `tokenIdBase` TEXT NOT NULL, + `tokenIdTarget` TEXT NOT NULL, + `reserveBase` TEXT NOT NULL, + `reserveTarget` TEXT NOT NULL, + `totalIssuance` TEXT NOT NULL, + `reservesAccount` TEXT NOT NULL, + PRIMARY KEY(`tokenIdBase`, `tokenIdTarget`)) + """.trimIndent() + ) + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `userpools` ( + `userTokenIdBase` TEXT NOT NULL, + `userTokenIdTarget` TEXT NOT NULL, + `accountAddress` TEXT NOT NULL, + `poolProvidersBalance` TEXT NOT NULL, + PRIMARY KEY(`userTokenIdBase`, `userTokenIdTarget`, `accountAddress`), + FOREIGN KEY(`userTokenIdBase`, `userTokenIdTarget`) REFERENCES `allpools`(`tokenIdBase`, `tokenIdTarget`) ON UPDATE NO ACTION ON DELETE CASCADE + ) + """.trimIndent() + ) + db.execSQL("CREATE INDEX IF NOT EXISTS `index_userpools_accountAddress` ON `userpools` (`accountAddress`)") } } From f4b5e87ff13faee648a1826c7f59861dbbf72332 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 18:35:18 +0500 Subject: [PATCH 72/84] version up --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6c41441d0c..6452e262d5 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 195 + versionCode = 196 // SDK and tools compileSdkVersion = 34 From 4ab6a7c42ae071e02cb9b2b0ba0a2528dfaac277 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 21:26:56 +0500 Subject: [PATCH 73/84] FLW-4777 There is no translation into Russia language --- common/src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 4ed02933a3..4ff05d100e 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -951,6 +951,7 @@ Валидаторы не найдены Посмотреть в %s Посмотреть кошелек + Все активы скрыты Купить %s с Получить Получить %s From 5ecd3d18e7e6c486cc7d4f9eebb26457367a261f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 21:27:32 +0500 Subject: [PATCH 74/84] fix onboarding config url for prod --- feature-onboarding-impl/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-onboarding-impl/build.gradle b/feature-onboarding-impl/build.gradle index 186c7e4625..80ad8391aa 100644 --- a/feature-onboarding-impl/build.gradle +++ b/feature-onboarding-impl/build.gradle @@ -22,7 +22,7 @@ android { } release { - buildConfigField "String", "ONBOARDING_CONFIG", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/develop-free/appConfigs/onboarding/mobile%20v2.json\"" + buildConfigField "String", "ONBOARDING_CONFIG", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/appConfigs/onboarding/mobile%20v2.json\"" } } From 860a5aabf613a76b0f7b8e0778ba37164e64d245 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Wed, 21 Aug 2024 21:28:09 +0500 Subject: [PATCH 75/84] version up --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6452e262d5..5b923178b1 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 196 + versionCode = 197 // SDK and tools compileSdkVersion = 34 From fea2f94a8a33c2a209fa7e301217ef4669c95055 Mon Sep 17 00:00:00 2001 From: Deneath Date: Thu, 22 Aug 2024 13:03:50 +0700 Subject: [PATCH 76/84] fixed existential deposit validation for EVM chains, hide NFT error Signed-off-by: Deneath --- build.gradle | 2 +- .../balance/list/BalanceListViewModel.kt | 18 ++++++++++-------- .../send/setup/SendSetupViewModel.kt | 11 +++++++---- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 5b923178b1..a6051023b7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 197 + versionCode = 198 // SDK and tools compileSdkVersion = 34 diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index ef4998da9e..52ca43f263 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -329,9 +329,10 @@ class BalanceListViewModel @Inject constructor( val screenModel = if (successfulCollections.isEmpty() && chainsWithFailedRequests.isNotEmpty()) { - withContext(Dispatchers.Main.immediate) { - showError(resourceManager.getString(R.string.nft_load_error)) - } + // todo move error state to the ndt list screen +// withContext(Dispatchers.Main.immediate) { +// showError(resourceManager.getString(R.string.nft_load_error)) +// } ScreenModel.ReadyToRender( result = successfulCollections, @@ -339,11 +340,12 @@ class BalanceListViewModel @Inject constructor( onItemClick = {} ) } else { - if (successfulCollections.isNotEmpty() && chainsWithFailedRequests.isNotEmpty()) { - withContext(Dispatchers.Main.immediate) { - showError("${resourceManager.getString(R.string.nft_load_error)} (${chainsWithFailedRequests.joinToString(", ")})") - } - } + // todo move error state to the ndt list screen +// if (successfulCollections.isNotEmpty() && chainsWithFailedRequests.isNotEmpty()) { +// withContext(Dispatchers.Main.immediate) { +// showError("${resourceManager.getString(R.string.nft_load_error)} (${chainsWithFailedRequests.joinToString(", ")})") +// } +// } ScreenModel.ReadyToRender( result = successfulCollections, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt index 17037e5c35..4d633f3898 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/send/setup/SendSetupViewModel.kt @@ -5,10 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import java.math.BigInteger -import java.math.RoundingMode -import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.NomisScoreInteractor import jp.co.soramitsu.account.api.domain.model.NomisScoreData import jp.co.soramitsu.common.address.AddressIconGenerator @@ -94,6 +90,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.math.BigInteger +import java.math.RoundingMode +import javax.inject.Inject @HiltViewModel class SendSetupViewModel @Inject constructor( @@ -569,6 +569,9 @@ class SendSetupViewModel @Inject constructor( isInputAddressValidFlow, addressInputTrimmedFlow ) { asset, amount, fee, isAddressValid, address -> + if (asset.token.configuration.ethereumType != null) { + return@combine Result.success(TransferValidationResult.Valid) + } if (amount.isZero()) { sendAllToggleState.value = ToggleState.INITIAL From d7605c66a85f1eb33951a7518c4e4e7ba1ab89a3 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Thu, 22 Aug 2024 15:23:11 +0500 Subject: [PATCH 77/84] FLW-4877 Wrong fee on the Pools --- .../LiquidityRemoveConfirmPresenter.kt | 59 +++---------------- 1 file changed, 8 insertions(+), 51 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 4d062bc651..67978ff444 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm -import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor -import jp.co.soramitsu.account.api.domain.model.address +import java.math.BigDecimal +import javax.inject.Inject import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance @@ -37,8 +36,6 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch -import java.math.BigDecimal -import javax.inject.Inject class LiquidityRemoveConfirmPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, @@ -46,13 +43,9 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( private val walletInteractor: WalletInteractor, private val chainsRepository: ChainsRepository, private val poolsInteractor: PoolsInteractor, - private val accountInteractor: AccountInteractor, private val resourceManager: ResourceManager, ) : LiquidityRemoveConfirmCallbacks { - private val _stateSlippage = MutableStateFlow(0.5) - val stateSlippage = _stateSlippage.asStateFlow() - private val screenArgsFlow = internalPoolsRouter.createNavGraphRoutesFlow() .filterIsInstance() .shareIn(coroutinesStore.uiScope, SharingStarted.Eagerly, 1) @@ -82,28 +75,10 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( it.first.asset.token to it.second.asset.token }.distinctUntilChanged() - private val isPoolPairEnabled = - screenArgsFlow.map { screenArgs -> - poolsInteractor.isPairEnabled( - baseTokenId = screenArgs.ids.first, - targetTokenId = screenArgs.ids.second - ) - } - - private val networkFeeFlow = combine( - screenArgsFlow, - tokensInPoolFlow, - stateSlippage, - isPoolPairEnabled - ) { screenArgs, (baseAsset, targetAsset), slippage, pairEnabled -> - getLiquidityNetworkFee( - tokenBase = baseAsset.configuration, - tokenTarget = targetAsset.configuration, - tokenBaseAmount = screenArgs.amountBase, - tokenTargetAmount = screenArgs.amountTarget, - pairEnabled = pairEnabled, - pairPresented = true, - slippageTolerance = slippage + private val networkFeeFlow = tokensInPoolFlow.map { (baseToken, targetToken) -> + getRemoveLiquidityNetworkFee( + tokenBase = baseToken.configuration, + tokenTarget = targetToken.configuration, ) } @@ -154,28 +129,10 @@ class LiquidityRemoveConfirmPresenter @Inject constructor( }.launchIn(coroutineScope) } - private suspend fun getLiquidityNetworkFee( - tokenBase: Asset, - tokenTarget: Asset, - tokenBaseAmount: BigDecimal, - tokenTargetAmount: BigDecimal, - pairEnabled: Boolean, - pairPresented: Boolean, - slippageTolerance: Double - ): BigDecimal { - val chainId = poolsInteractor.poolsChainId - val soraChain = walletInteractor.getChain(chainId) - val user = accountInteractor.selectedMetaAccount().address(soraChain).orEmpty() - val result = poolsInteractor.calcAddLiquidityNetworkFee( - chainId, - user, + private suspend fun getRemoveLiquidityNetworkFee(tokenBase: Asset, tokenTarget: Asset): BigDecimal { + val result = poolsInteractor.calcRemoveLiquidityNetworkFee( tokenBase, tokenTarget, - tokenBaseAmount, - tokenTargetAmount, - pairEnabled, - pairPresented, - slippageTolerance, ) return result ?: BigDecimal.ZERO } From 3c320540076b8df55d7a04862fdb50f0659fa71f Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Thu, 22 Aug 2024 15:47:09 +0500 Subject: [PATCH 78/84] FLW-4874 There is not Sumimasen screen on the pool's screen --- common/src/main/res/values-in/strings.xml | 3 +- common/src/main/res/values-ja/strings.xml | 3 +- common/src/main/res/values-pt/strings.xml | 3 +- common/src/main/res/values-ru/strings.xml | 3 +- common/src/main/res/values-tr/strings.xml | 3 +- common/src/main/res/values-vi/strings.xml | 21 ++++++++++- common/src/main/res/values-zh/strings.xml | 3 +- common/src/main/res/values/strings.xml | 3 +- .../presentation/poollist/PoolListScreen.kt | 37 ++++++++++++------- .../assetselector/AssetSelectContent.kt | 2 +- .../chainselector/ChainSelectContent.kt | 2 +- .../searchAssets/SearchAssetsScreen.kt | 2 +- 12 files changed, 61 insertions(+), 24 deletions(-) diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index 76e9b0decf..32d11d8d63 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -271,8 +271,9 @@ Rute Simpan Cari - Tidak ada jaringan atau aset yang ditemukan :( + Tidak ada aset yang ditemukan :( Sumimasen! + Tidak ada jaringan atau aset yang ditemukan :( Tidak ada hasil pencarian. \n Pastikan Anda mengetikkan alamat akun lengkap Hasil pencarian: %d Hasil pencarian akan muncul di sini diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 98cf6164a9..bd64dc6220 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -262,8 +262,9 @@ ルート 保存 検索 - ネットワークまたはアセットが見つかりませんでした + アセットが見つかりませんでした 失礼しました + ネットワークまたはアセットが見つかりませんでした 検索結果はありません。 \n完全なアカウント・アドレスを入力したことを確認してください 検索結果: %d 検索結果はここに表示されます diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 8207fd3982..368110ef6c 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -262,8 +262,9 @@ Rota Salvar Pesquisa - Nenhuma rede ou ativo foi encontrado :( + Nenhum ativo foi encontrado :( Sumimasen! + Nenhuma rede ou ativo foi encontrado :( Pesquisa sem resultados. \n Certifique-se que digita o endereço completo da conta Resultados da pesquisa: %d Os resultados da pesquisa aparecerão aqui diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 4ff05d100e..c4ada2b60d 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -239,8 +239,9 @@ Направление Сохранить Поиск - Не найдено ни одной сети или ассета :( + Ассеты не найдены :( Извините! + Не найдено ни одной сети или ассета :( Поиск не дал результатов.\nПроверьте, что вы указали полный адрес аккаунта Результаты поиска: %d Здесь появятся результаты поиска diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 77ebfbd21e..2a59e39da2 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -271,8 +271,9 @@ Rota Kaydetmek Arama - Hiçbir ağ veya varlık bulunamadı :( + Hiçbir Öğe bulunamadı:( Sumimasen! + Hiçbir ağ veya varlık bulunamadı :( Arama sonucu bulunamadı. \n Tam hesap adresini yazdığınızdan emin olun Arama sonuçları: %d Arama sonuçları burada görünecek. diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index 341cafe832..5d9575c032 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -47,6 +47,19 @@ Không tìm thấy tài khoản Tùy chọn tài khoản Tài khoản với khóa bí mật chia sẻ + Trung bình thời gian giao dịch + Điểm Multichain của bạn dựa trên hành trình onchain của bạn thông qua 3 hệ sinh thái: Ethereum, Polygon và Binance Smart Chain + Không thể truy xuất thông tin điểm tài khoản. Vui lòng thử lại sau. + Token USD nắm giữ + Thời gian giao dịch tối đa + Thời gian giao dịch tối thiểu + Số dư gốc USD + Giao dịch bị từ chối + Số điểm của bạn + Tổng số giao dịch + Đã cập nhật + Tuổi ví + Hiển thị điểm ví tài khoản %s Thêm tài khoản Tài khoản để xuất @@ -276,8 +289,9 @@ Route Lưu Tìm kiếm - Không có mạng hoặc tài sản nào được tìm thấy :( + Không có tài sản nào được tìm thấy :( Sumimasen! + Không có mạng hoặc tài sản nào được tìm thấy :( Không tìm thấy kết quả.\n Hãy chắc chắn bạn nhập địa chỉ tài khoản đầy đủ Kết quả tìm kiếm: %d Kết quả tìm kiếm sẽ xuất hiện tại đây @@ -703,6 +717,7 @@ Tổng số bạn stake Đã đạt đến giới hạn pool trong mạng này Bạn không thể tạo thêm pool + Điểm nomis đa chuỗi Tài khoản Dapps và hơn thế nữa... Tính năng thử nghiệm @@ -747,6 +762,10 @@ Bổ sung: Chúng tôi thực sự khuyên bạn không nên gửi %s vào tài khoản này. Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. + Địa chỉ bạn sắp chuyển đến có hoạt động onchain thấp, điều này có thể chỉ ra kẻ lừa đảo hoặc kẻ tấn công sybil tiềm ẩn + Điểm nomis đa chuỗi + Mạng ít hoạt động + Tiến hành thận trọng Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. Chúng tôi thực sự khuyên bạn không nên gửi {asset} tới tài khoản này. Quét mã từ người nhận Quét mã QR diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml index 1a00c8c562..7571540ea3 100644 --- a/common/src/main/res/values-zh/strings.xml +++ b/common/src/main/res/values-zh/strings.xml @@ -275,8 +275,9 @@ 路径 保存 搜索 - 未找到任何网络或资产 :( + 未找到任何资产 :( 对不起! + 未找到任何网络或资产 :( 没有搜索结果。\n请确保您输入了完整的账户地址 搜索结果:%d 搜索结果将会在这里显示 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index d58caa1169..9fbd9b77d7 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -298,8 +298,9 @@ Route Save Search - No networks or assets were found :( + No assets were found :( Sumimasen! + No networks or assets were found :( No search results.\nMake sure you type the full account address Search results: %d Search results will appear here diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt index 5d305e13d2..a79a9a1405 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/poollist/PoolListScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import jp.co.soramitsu.androidfoundation.format.StringPair import jp.co.soramitsu.common.compose.component.CorneredInput +import jp.co.soramitsu.common.compose.component.EmptyMessage import jp.co.soramitsu.common.compose.component.MarginVertical import jp.co.soramitsu.common.compose.models.TextModel import jp.co.soramitsu.common.compose.theme.white04 @@ -60,19 +61,28 @@ fun PoolListScreen(state: PoolListState, callback: PoolListScreenInterface) { if (state.isLoading) { ShimmerPoolList(SHIMMERS_SIZE) } else { - val listState = rememberLazyListState() + if (state.pools.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + EmptyMessage(message = R.string.common_search_network_and_assets_alert_description) + } + } else { + val listState = rememberLazyListState() - LazyColumn( - state = listState, - modifier = Modifier - .wrapContentHeight() - ) { - items(state.pools) { pool -> - BasicPoolListItem( - modifier = Modifier.padding(vertical = Dimens.x1), - state = pool, - onPoolClick = callback::onPoolClicked, - ) + LazyColumn( + state = listState, + modifier = Modifier + .wrapContentHeight() + ) { + items(state.pools) { pool -> + BasicPoolListItem( + modifier = Modifier.padding(vertical = Dimens.x1), + state = pool, + onPoolClick = callback::onPoolClicked, + ) + } } } } @@ -99,7 +109,8 @@ private fun PreviewPoolListScreen() { ) PoolListScreen( state = PoolListState( - pools = items, + pools = emptyList(), + isLoading = false ), callback = object : PoolListScreenInterface { override fun onPoolClicked(pair: StringPair) {} diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetselector/AssetSelectContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetselector/AssetSelectContent.kt index d4267b9400..7782fcbd67 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetselector/AssetSelectContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/assetselector/AssetSelectContent.kt @@ -110,7 +110,7 @@ fun EmptyResultContent() { ) H3(text = stringResource(id = R.string.common_search_assets_alert_title)) B0( - text = stringResource(id = R.string.common_search_assets_alert_description), + text = stringResource(id = R.string.common_search_network_and_assets_alert_description), color = gray2 ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/chainselector/ChainSelectContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/chainselector/ChainSelectContent.kt index f861ffadc3..315021fc24 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/chainselector/ChainSelectContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/chainselector/ChainSelectContent.kt @@ -159,7 +159,7 @@ fun EmptyResultContent() { MarginVertical(margin = 16.dp) B0( - text = stringResource(id = jp.co.soramitsu.common.R.string.common_search_assets_alert_description), + text = stringResource(id = jp.co.soramitsu.common.R.string.common_search_network_and_assets_alert_description), color = white50 ) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/searchAssets/SearchAssetsScreen.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/searchAssets/SearchAssetsScreen.kt index f9efb2ce39..95357a6416 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/searchAssets/SearchAssetsScreen.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/searchAssets/SearchAssetsScreen.kt @@ -88,7 +88,7 @@ fun SearchAssetsScreen( data?.assets == null -> {} data.assets.isEmpty() -> { MarginVertical(margin = 16.dp) - EmptyMessage(message = R.string.common_search_assets_alert_description) + EmptyMessage(message = R.string.common_search_network_and_assets_alert_description) } else -> { From e2613f9fbc89aa38166d111e389e74d63262ff8b Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 23 Aug 2024 10:43:55 +0700 Subject: [PATCH 79/84] possible fix of crash on pools Signed-off-by: Deneath --- .../jp/co/soramitsu/app/root/navigation/Navigator.kt | 8 ++++---- .../staking/impl/presentation/StakingRouter.kt | 2 +- .../common/StakingPoolSharedStateProvider.kt | 4 ++++ .../impl/presentation/pools/PoolInfoFragment.kt | 5 ++--- .../impl/presentation/pools/PoolInfoViewModel.kt | 5 +++-- .../impl/presentation/pools/SelectPoolViewModel.kt | 9 +++++++-- .../staking/balance/ManagePoolStakeViewModel.kt | 12 ++++++++---- .../presentation/staking/main/StakingViewModel.kt | 11 +++++------ 8 files changed, 34 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 420905d60e..ca2f8f111c 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -16,7 +16,6 @@ import co.jp.soramitsu.walletconnect.domain.WalletConnectRouter import co.jp.soramitsu.walletconnect.model.ChainChooseResult import co.jp.soramitsu.walletconnect.model.ChainChooseState import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateSignerPayload -import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.model.ImportMode import jp.co.soramitsu.account.api.presentation.account.create.ChainAccountCreatePayload import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload @@ -170,7 +169,6 @@ import jp.co.soramitsu.walletconnect.impl.presentation.requestpreview.RequestPre import jp.co.soramitsu.walletconnect.impl.presentation.sessionproposal.SessionProposalFragment import jp.co.soramitsu.walletconnect.impl.presentation.sessionrequest.SessionRequestFragment import jp.co.soramitsu.walletconnect.impl.presentation.transactionrawdata.RawDataFragment -import kotlin.coroutines.coroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -186,6 +184,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.job import kotlinx.parcelize.Parcelize +import java.math.BigDecimal +import kotlin.coroutines.coroutineContext @Parcelize class NavComponentDelayedNavigation(val globalActionId: Int, val extras: Bundle? = null) : DelayedNavigation @@ -617,8 +617,8 @@ class Navigator : navController?.navigate(R.id.confirmJoinPoolFragment) } - override fun openPoolInfo(poolInfo: PoolInfo) { - navController?.navigate(R.id.poolInfoFragment, PoolInfoFragment.getBundle(poolInfo)) + override fun openPoolInfo(poolId: Int) { + navController?.navigate(R.id.poolInfoFragment, PoolInfoFragment.getBundle(poolId)) } override fun openManagePoolStake() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt index ace3c7d14c..8b2e3beea9 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt @@ -111,7 +111,7 @@ interface StakingRouter { fun openConfirmJoinPool() - fun openPoolInfo(poolInfo: PoolInfo) + fun openPoolInfo(poolId: Int) fun openManagePoolStake() diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/common/StakingPoolSharedStateProvider.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/common/StakingPoolSharedStateProvider.kt index e0eaf0958b..f830461d62 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/common/StakingPoolSharedStateProvider.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/common/StakingPoolSharedStateProvider.kt @@ -1,5 +1,8 @@ package jp.co.soramitsu.staking.impl.presentation.common +import jp.co.soramitsu.staking.api.domain.model.PoolInfo +import kotlinx.coroutines.flow.MutableStateFlow + class StakingPoolSharedStateProvider { val mainState by lazy { StakingPoolSharedState() } val joinFlowState by lazy { StakingPoolSharedState() } @@ -8,6 +11,7 @@ class StakingPoolSharedStateProvider { val selectValidatorsState by lazy { StakingPoolSharedState() } val selectedValidatorsState by lazy { StakingPoolSharedState() } val editPoolState by lazy { StakingPoolSharedState() } + val poolsCache: MutableStateFlow> = MutableStateFlow(emptyMap()) val requireMainState: StakingPoolState get() = requireNotNull(mainState.get()) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoFragment.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoFragment.kt index c2ea0ac6aa..4cff4bd502 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoFragment.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoFragment.kt @@ -10,7 +10,6 @@ import androidx.fragment.app.viewModels import com.google.android.material.bottomsheet.BottomSheetBehavior import dagger.hilt.android.AndroidEntryPoint import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment -import jp.co.soramitsu.staking.api.domain.model.PoolInfo import jp.co.soramitsu.staking.impl.presentation.pools.compose.PoolInfoScreen @AndroidEntryPoint @@ -18,8 +17,8 @@ class PoolInfoFragment : BaseComposeBottomSheetDialogFragment companion object { const val POOL_INFO_KEY = "poolInfo" - fun getBundle(poolInfo: PoolInfo) = Bundle().apply { - putParcelable(POOL_INFO_KEY, poolInfo) + fun getBundle(poolId: Int) = Bundle().apply { + putInt(POOL_INFO_KEY, poolId) } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoViewModel.kt index 62bfcedb13..7566d6853f 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/PoolInfoViewModel.kt @@ -3,7 +3,6 @@ package jp.co.soramitsu.staking.impl.presentation.pools import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.DropDownViewState import jp.co.soramitsu.common.compose.component.TitleValueViewState @@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject @HiltViewModel class PoolInfoViewModel @Inject constructor( @@ -59,7 +59,8 @@ class PoolInfoViewModel @Inject constructor( chain = mainState.requireChain asset = mainState.requireAsset currentUserAccountId = chain.accountIdOf(mainState.requireAddress) - poolInfo = requireNotNull(savedStateHandle.get(PoolInfoFragment.POOL_INFO_KEY)) + val poolId = savedStateHandle.get(PoolInfoFragment.POOL_INFO_KEY) + poolInfo = requireNotNull(stakingPoolSharedStateProvider.poolsCache.value[poolId]) canChangeRoles = poolInfo.root.contentEquals(currentUserAccountId) val stakedAmount = asset.token.amountFromPlanks(poolInfo.stakedInPlanks) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/SelectPoolViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/SelectPoolViewModel.kt index 64cf74b7ee..38495d2025 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/SelectPoolViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/pools/SelectPoolViewModel.kt @@ -5,7 +5,6 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.theme.black1 import jp.co.soramitsu.common.compose.theme.greenText @@ -32,6 +31,8 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import javax.inject.Inject @HiltViewModel class SelectPoolViewModel @Inject constructor( @@ -113,7 +114,11 @@ class SelectPoolViewModel @Inject constructor( val selectedPoolId = requireNotNull(item.id) val pools = (poolsFlow.value as? LoadingState.Loaded)?.data val pool = requireNotNull(pools?.find { it.poolId == selectedPoolId.toBigInteger() }) - router.openPoolInfo(pool) + + stakingPoolSharedStateProvider.poolsCache.update { prevState -> + prevState + (pool.poolId.toInt() to pool) + } + router.openPoolInfo(pool.poolId.toInt()) } fun onNextClick() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/ManagePoolStakeViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/ManagePoolStakeViewModel.kt index e24212f79a..7785870f83 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/ManagePoolStakeViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/ManagePoolStakeViewModel.kt @@ -2,8 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.staking.balance import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigInteger -import javax.inject.Inject import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.NotificationState import jp.co.soramitsu.common.compose.component.TitleValueViewState @@ -36,7 +34,10 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigInteger +import javax.inject.Inject @HiltViewModel class ManagePoolStakeViewModel @Inject constructor( @@ -62,7 +63,7 @@ class ManagePoolStakeViewModel @Inject constructor( userRole = pool?.getUserRole(accountId) ) } - } + }.stateIn(viewModelScope, SharingStarted.Eagerly, null) private val validatorIdsFlow = poolStateFlow.filterNotNull() .map { pool -> stakingPoolInteractor.getValidatorsIds(chain, pool.poolId) } @@ -225,7 +226,10 @@ class ManagePoolStakeViewModel @Inject constructor( private fun onPoolInfoClick() { viewModelScope.launch { val pool = requireNotNull(poolStateFlow.first { it != null }) - router.openPoolInfo(pool.toPoolInfo()) + stakingPoolSharedStateProvider.poolsCache.update { prevState -> + prevState + (pool.poolId.toInt() to pool.toPoolInfo()) + } + router.openPoolInfo(pool.poolId.toInt()) } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewModel.kt index 428879075f..8b2b4f9102 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewModel.kt @@ -4,9 +4,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigDecimal -import javax.inject.Inject -import javax.inject.Named import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.address.AddressModel import jp.co.soramitsu.common.base.BaseViewModel @@ -22,12 +19,10 @@ import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.Event import jp.co.soramitsu.common.utils.childScope import jp.co.soramitsu.common.utils.formatAsPercentage -import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.withLoading import jp.co.soramitsu.common.validation.ValidationExecutor import jp.co.soramitsu.core.updater.UpdateSystem -import jp.co.soramitsu.core.utils.amountFromPlanks import jp.co.soramitsu.feature_staking_impl.R import jp.co.soramitsu.staking.api.data.StakingAssetSelection import jp.co.soramitsu.staking.api.data.StakingSharedState @@ -64,7 +59,6 @@ import jp.co.soramitsu.staking.impl.scenarios.StakingPoolInteractor import jp.co.soramitsu.staking.impl.scenarios.parachain.StakingParachainScenarioInteractor import jp.co.soramitsu.staking.impl.scenarios.relaychain.StakingRelayChainScenarioInteractor import jp.co.soramitsu.wallet.impl.domain.interfaces.QuickInputsUseCase -import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import jp.co.soramitsu.wallet.impl.presentation.model.ControllerDeprecationWarningModel import jp.co.soramitsu.wallet.impl.presentation.model.toModel import kotlinx.coroutines.CoroutineScope @@ -88,6 +82,9 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject +import javax.inject.Named private const val CURRENT_ICON_SIZE = 40 @@ -225,6 +222,8 @@ class StakingViewModel @Inject constructor( stakingSharedState.selectionItem.distinctUntilChanged().onEach { setupStakingSharedState.set(SetupStakingProcess.Initial(it.type)) stakingStateScope.coroutineContext.cancelChildren() + + stakingPoolSharedStateProvider.poolsCache.update { emptyMap() } }.launchIn(viewModelScope) interactor.selectionStateFlow().onEach { From 79e410e6e53ea55471a60f4725b9a4a9cb634b73 Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 23 Aug 2024 14:04:36 +0700 Subject: [PATCH 80/84] version up Signed-off-by: Deneath --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a6051023b7..0a114f8998 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 198 + versionCode = 199 // SDK and tools compileSdkVersion = 34 From c7bb3a80ac415e90b43fb8f291c45f9d79d653bd Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 23 Aug 2024 12:48:37 +0500 Subject: [PATCH 81/84] detekt fix --- .../liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt index 67978ff444..0527f1e7bb 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremoveconfirm/LiquidityRemoveConfirmPresenter.kt @@ -1,7 +1,5 @@ package jp.co.soramitsu.liquiditypools.impl.presentation.liquidityremoveconfirm -import java.math.BigDecimal -import javax.inject.Inject import jp.co.soramitsu.common.compose.component.FeeInfoViewState import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.applyFiatRate @@ -36,6 +34,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject class LiquidityRemoveConfirmPresenter @Inject constructor( private val coroutinesStore: CoroutinesStore, From 1aa4362b8c530054b6cb90bbdbf422d7afd8eaa4 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 23 Aug 2024 13:30:20 +0500 Subject: [PATCH 82/84] fix remove liquidity amount calculations --- .../liquidityremove/LiquidityRemovePresenter.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt index 2cabbd3021..a835dd58ad 100644 --- a/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt +++ b/feature-liquiditypools-impl/src/main/java/jp/co/soramitsu/liquiditypools/impl/presentation/liquidityremove/LiquidityRemovePresenter.kt @@ -306,14 +306,14 @@ class LiquidityRemovePresenter @Inject constructor( .debounce(INPUT_DEBOUNCE) .onEach { amount -> poolDataUsable?.let { - amountBase = if (it.user.basePooled <= amount) amount else it.user.basePooled + amountBase = if (amount <= it.user.basePooled) amount else it.user.basePooled - val precisionTo = poolDataFlow.firstOrNull()?.basic?.targetToken?.precision + val precisionTarget = poolDataFlow.firstOrNull()?.basic?.targetToken?.precision amountTarget = PolkaswapFormulas.calculateOneAmountFromAnother( amountBase, it.user.basePooled, it.user.targetPooled, - precisionTo + precisionTarget ) percent = PolkaswapFormulas.calculateShareOfPoolFromAmount( amountBase, @@ -333,7 +333,7 @@ class LiquidityRemovePresenter @Inject constructor( targetAmountInputViewState = stateFlow.value.targetAmountInputViewState.copy( fiatAmount = it.applyFiatRate(targetToken.fiatRate)?.formatFiat(targetToken.fiatSymbol), tokenAmount = it - ), + ) ) } .debounce(INPUT_DEBOUNCE) From 1d85be4451fc464b52bcb34e3b2bce469ced4c29 Mon Sep 17 00:00:00 2001 From: Sergey Pankratov Date: Fri, 23 Aug 2024 13:30:50 +0500 Subject: [PATCH 83/84] version up --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0a114f8998..fdda2f6158 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { // App version versionName = '3.7.1' - versionCode = 199 + versionCode = 200 // SDK and tools compileSdkVersion = 34 From 22a0d8c083633f2d3ceae6d8b3dcbd4291215a1f Mon Sep 17 00:00:00 2001 From: Deneath Date: Fri, 23 Aug 2024 17:20:26 +0700 Subject: [PATCH 84/84] fixed xcm origin fee, fixed crash on validator details Signed-off-by: Deneath --- .../app/root/navigation/Navigator.kt | 5 ++--- .../staking/impl/domain/StakingInteractor.kt | 4 ++++ .../impl/presentation/StakingRouter.kt | 3 +-- .../ConfirmNominationsViewModel.kt | 10 ++++++--- .../review/ReviewCustomValidatorsViewModel.kt | 10 ++++++--- .../search/SearchCustomValidatorsViewModel.kt | 16 +++++++++----- .../select/SelectCustomValidatorsViewModel.kt | 12 +++++++---- .../SelectCustomValidatorsViewModel.kt | 18 +++++++--------- .../RecommendedValidatorsViewModel.kt | 8 +++++-- .../compose/SelectValidatorsViewModel.kt | 21 ++++++++++--------- .../compose/SelectedValidatorsViewModel.kt | 12 ++++++++--- .../current/CurrentValidatorsViewModel.kt | 6 +++++- .../details/ValidatorDetailsFragment.kt | 5 ++--- .../details/ValidatorDetailsViewModel.kt | 6 +++--- .../wallet/impl/di/WalletFeatureModule.kt | 4 ++-- .../impl/domain/QuickInputsUseCaseImpl.kt | 13 +++++++----- gradle/libs.versions.toml | 2 +- 17 files changed, 95 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index ca2f8f111c..2be700acc0 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -122,7 +122,6 @@ import jp.co.soramitsu.staking.impl.presentation.validators.change.custom.settin import jp.co.soramitsu.staking.impl.presentation.validators.details.CollatorDetailsFragment import jp.co.soramitsu.staking.impl.presentation.validators.details.ValidatorDetailsFragment import jp.co.soramitsu.staking.impl.presentation.validators.parcel.CollatorDetailsParcelModel -import jp.co.soramitsu.staking.impl.presentation.validators.parcel.ValidatorDetailsParcelModel import jp.co.soramitsu.success.presentation.SuccessFragment import jp.co.soramitsu.success.presentation.SuccessRouter import jp.co.soramitsu.wallet.api.domain.model.XcmChainType @@ -731,8 +730,8 @@ class Navigator : navController?.navigate(R.id.close_swap) } - override fun openValidatorDetails(validatorDetails: ValidatorDetailsParcelModel) { - navController?.navigate(R.id.validatorDetailsFragment, ValidatorDetailsFragment.getBundle(validatorDetails)) + override fun openValidatorDetails(validatorIdHex: String) { + navController?.navigate(R.id.validatorDetailsFragment, ValidatorDetailsFragment.getBundle(validatorIdHex)) } override fun openSelectedValidators() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/StakingInteractor.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/StakingInteractor.kt index e5ba140b21..c960db0bbf 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/StakingInteractor.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/StakingInteractor.kt @@ -33,12 +33,14 @@ import jp.co.soramitsu.staking.impl.data.mappers.mapAccountToStakingAccount import jp.co.soramitsu.staking.impl.data.repository.StakingRewardsRepository import jp.co.soramitsu.staking.impl.domain.validations.setup.SetupStakingFeeValidation import jp.co.soramitsu.staking.impl.domain.validations.setup.SetupStakingValidationFailure +import jp.co.soramitsu.staking.impl.presentation.validators.parcel.ValidatorDetailsParcelModel import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.ControllerDeprecationWarning import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.validation.EnoughToPayFeesValidation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first @@ -58,6 +60,8 @@ class StakingInteractor( private val addressIconGenerator: AddressIconGenerator, private val walletRepository: WalletRepository ) { + val validatorDetailsCache = MutableStateFlow>(emptyMap()) + suspend fun getCurrentMetaAccount() = accountRepository.getSelectedMetaAccount() fun selectedMetaAccountFlow() = accountRepository.selectedMetaAccountFlow() suspend fun getMetaAccount(metaId: Long) = accountRepository.getMetaAccount(metaId) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt index 8b2e3beea9..45873426ee 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/StakingRouter.kt @@ -18,7 +18,6 @@ import jp.co.soramitsu.staking.impl.presentation.staking.rewardDestination.confi import jp.co.soramitsu.staking.impl.presentation.staking.unbond.confirm.ConfirmUnbondPayload import jp.co.soramitsu.staking.impl.presentation.staking.unbond.select.SelectUnbondPayload import jp.co.soramitsu.staking.impl.presentation.validators.parcel.CollatorDetailsParcelModel -import jp.co.soramitsu.staking.impl.presentation.validators.parcel.ValidatorDetailsParcelModel import kotlinx.coroutines.flow.Flow interface StakingRouter { @@ -49,7 +48,7 @@ interface StakingRouter { fun openReviewCustomValidators() - fun openValidatorDetails(validatorDetails: ValidatorDetailsParcelModel) + fun openValidatorDetails(validatorIdHex: String) fun openSelectedValidators() diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/nominations/ConfirmNominationsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/nominations/ConfirmNominationsViewModel.kt index 5c2930fe5e..03df357aa0 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/nominations/ConfirmNominationsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/nominations/ConfirmNominationsViewModel.kt @@ -4,8 +4,6 @@ import androidx.lifecycle.liveData import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import javax.inject.Named import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.resources.ResourceManager @@ -23,7 +21,10 @@ import jp.co.soramitsu.staking.impl.presentation.validators.findSelectedValidato import jp.co.soramitsu.wallet.impl.domain.TokenUseCase import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Named @HiltViewModel class ConfirmNominationsViewModel @Inject constructor( @@ -54,7 +55,10 @@ class ConfirmNominationsViewModel @Inject constructor( fun validatorInfoClicked(validatorModel: ValidatorModel) { viewModelScope.launch { validators.findSelectedValidator(validatorModel.accountIdHex)?.let { - router.openValidatorDetails(mapValidatorToValidatorDetailsParcelModel(it)) + interactor.validatorDetailsCache.update { prev -> + prev + (it.accountIdHex to mapValidatorToValidatorDetailsParcelModel(it)) + } + router.openValidatorDetails(it.accountIdHex) } } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt index a962011bd0..ddec49d7f3 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt @@ -2,8 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.change.custom.revie import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import javax.inject.Named import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.resources.ResourceManager @@ -29,7 +27,10 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Named @HiltViewModel class ReviewCustomValidatorsViewModel @Inject constructor( @@ -114,7 +115,10 @@ class ReviewCustomValidatorsViewModel @Inject constructor( } fun validatorInfoClicked(validatorModel: ValidatorModel) { - router.openValidatorDetails(mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + interactor.validatorDetailsCache.update { prev -> + prev + (validatorModel.accountIdHex to mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + } + router.openValidatorDetails(validatorModel.accountIdHex) } fun nextClicked() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt index 6e95b62baf..2f4bd03911 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt @@ -2,9 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.change.custom.searc import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.util.Locale -import javax.inject.Inject -import javax.inject.Named import jp.co.soramitsu.common.address.AddressIconGenerator.Companion.SIZE_MEDIUM import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.presentation.LoadingState @@ -16,6 +13,7 @@ import jp.co.soramitsu.feature_staking_impl.R import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.extensions.requireHexPrefix import jp.co.soramitsu.shared_utils.extensions.toHexString +import jp.co.soramitsu.staking.impl.domain.StakingInteractor import jp.co.soramitsu.staking.impl.domain.validators.current.search.BlockedValidatorException import jp.co.soramitsu.staking.impl.domain.validators.current.search.SearchCustomBlockProducerInteractor import jp.co.soramitsu.staking.impl.presentation.StakingRouter @@ -33,7 +31,11 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.util.Locale +import javax.inject.Inject +import javax.inject.Named sealed class SearchBlockProducersState { object NoInput : SearchBlockProducersState() @@ -51,7 +53,8 @@ class SearchCustomValidatorsViewModel @Inject constructor( private val resourceManager: ResourceManager, private val sharedStateSetup: SetupStakingSharedState, @Named("StakingTokenUseCase") tokenUseCase: TokenUseCase, - private val searchCustomBlockProducerInteractor: SearchCustomBlockProducerInteractor + private val searchCustomBlockProducerInteractor: SearchCustomBlockProducerInteractor, + private val interactor: StakingInteractor ) : BaseViewModel() { private val confirmSetupState = sharedStateSetup.setupStakingProcess @@ -167,7 +170,10 @@ class SearchCustomValidatorsViewModel @Inject constructor( ) }, { - router.openValidatorDetails(mapValidatorToValidatorDetailsParcelModel(it)) + interactor.validatorDetailsCache.update { prev -> + prev + (it.accountIdHex to mapValidatorToValidatorDetailsParcelModel(it)) + } + router.openValidatorDetails(it.accountIdHex) } ) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt index 77a1e9084c..30768bb0c8 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt @@ -2,9 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.change.custom.selec import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigInteger -import javax.inject.Inject -import javax.inject.Named import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.address.AddressModel import jp.co.soramitsu.common.address.createAddressModel @@ -45,7 +42,11 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigInteger +import javax.inject.Inject +import javax.inject.Named @HiltViewModel class SelectCustomValidatorsViewModel @Inject constructor( @@ -180,7 +181,10 @@ class SelectCustomValidatorsViewModel @Inject constructor( } fun validatorInfoClicked(validatorModel: ValidatorModel) { - router.openValidatorDetails(mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + interactor.validatorDetailsCache.update { prev -> + prev + (validatorModel.accountIdHex to mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + } + router.openValidatorDetails(validatorModel.accountIdHex) } fun validatorClicked(validatorModel: ValidatorModel) { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/compose/SelectCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/compose/SelectCustomValidatorsViewModel.kt index 59a186e9d4..d2667465d5 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/compose/SelectCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/custom/select/compose/SelectCustomValidatorsViewModel.kt @@ -3,8 +3,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.change.custom.selec import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigInteger -import javax.inject.Inject import jp.co.soramitsu.common.AlertViewState import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.presentation.LoadingState @@ -49,6 +47,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigInteger +import javax.inject.Inject private val filtersSet = setOf(Filters.HavingOnChainIdentity, Filters.NotSlashedFilter, Filters.NotOverSubscribed) @@ -222,14 +222,12 @@ class SelectCustomValidatorsViewModel @Inject constructor( override fun onInfoClick(item: SelectableListItemState) { val validator = recommendedValidators.value.dataOrNull()?.find { it.accountIdHex == item.id } - - router.openValidatorDetails( - mapValidatorToValidatorDetailsParcelModel( - requireNotNull( - validator - ) - ) - ) + validator?.let { + interactor.validatorDetailsCache.update { prev -> + prev + (it.accountIdHex to mapValidatorToValidatorDetailsParcelModel(it)) + } + router.openValidatorDetails(it.accountIdHex) + } } override fun onChooseClick() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt index 15f95a000b..400e223977 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt @@ -2,7 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.change.recommended import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.resources.ResourceManager @@ -29,7 +28,9 @@ import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject import javax.inject.Named @HiltViewModel @@ -73,7 +74,10 @@ class RecommendedValidatorsViewModel @Inject constructor( } fun validatorInfoClicked(validatorModel: ValidatorModel) { - router.openValidatorDetails(mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + interactor.validatorDetailsCache.update { prev -> + prev + (validatorModel.validator.accountIdHex to mapValidatorToValidatorDetailsParcelModel(validatorModel.validator)) + } + router.openValidatorDetails(validatorModel.validator.accountIdHex) } fun nextClicked() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectValidatorsViewModel.kt index d4ed1d8742..ee1844e71e 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectValidatorsViewModel.kt @@ -2,8 +2,6 @@ package jp.co.soramitsu.staking.impl.presentation.validators.compose import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import java.math.BigInteger -import javax.inject.Inject import jp.co.soramitsu.common.AlertViewState import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.presentation.LoadingState @@ -18,6 +16,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.shared_utils.extensions.fromHex import jp.co.soramitsu.shared_utils.extensions.toHexString import jp.co.soramitsu.staking.api.domain.model.Validator +import jp.co.soramitsu.staking.impl.domain.StakingInteractor import jp.co.soramitsu.staking.impl.domain.recommendations.ValidatorRecommendatorFactory import jp.co.soramitsu.staking.impl.domain.recommendations.settings.RecommendationSettings import jp.co.soramitsu.staking.impl.domain.recommendations.settings.RecommendationSettingsProvider @@ -43,6 +42,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigInteger +import javax.inject.Inject private val filtersSet = setOf(Filters.HavingOnChainIdentity, Filters.NotSlashedFilter, Filters.NotOverSubscribed) @@ -56,7 +57,8 @@ class SelectValidatorsViewModel @Inject constructor( private val recommendationSettingsProviderFactory: RecommendationSettingsProviderFactory, private val resourceManager: ResourceManager, private val stakingPoolSharedStateProvider: StakingPoolSharedStateProvider, - private val settingsStorage: SettingsStorage + private val settingsStorage: SettingsStorage, + private val interactor: StakingInteractor ) : BaseViewModel(), SelectValidatorsScreenInterface { private val asset: Asset @@ -195,13 +197,12 @@ class SelectValidatorsViewModel @Inject constructor( override fun onInfoClick(item: SelectableListItemState) { val validator = recommendedValidators.value.dataOrNull()?.find { it.accountIdHex == item.id } - router.openValidatorDetails( - mapValidatorToValidatorDetailsParcelModel( - requireNotNull( - validator - ) - ) - ) + validator?.let { + interactor.validatorDetailsCache.update { prev -> + prev + (it.accountIdHex to mapValidatorToValidatorDetailsParcelModel(it)) + } + router.openValidatorDetails(it.accountIdHex) + } } override fun onChooseClick() { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectedValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectedValidatorsViewModel.kt index 345713698b..563271d701 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectedValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/compose/SelectedValidatorsViewModel.kt @@ -11,12 +11,14 @@ import jp.co.soramitsu.feature_staking_impl.R import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.shared_utils.runtime.AccountId import jp.co.soramitsu.staking.api.domain.model.NominatedValidator +import jp.co.soramitsu.staking.impl.domain.StakingInteractor import jp.co.soramitsu.staking.impl.domain.recommendations.settings.sortings.BlockProducersSorting import jp.co.soramitsu.staking.impl.presentation.StakingRouter import jp.co.soramitsu.staking.impl.presentation.common.SelectValidatorFlowState import jp.co.soramitsu.staking.impl.presentation.common.StakingPoolSharedStateProvider import jp.co.soramitsu.staking.impl.presentation.mappers.mapValidatorToValidatorDetailsWithStakeFlagParcelModel import jp.co.soramitsu.staking.impl.presentation.pools.compose.SelectableListItemState +import jp.co.soramitsu.staking.impl.presentation.validators.toModel import jp.co.soramitsu.staking.impl.scenarios.StakingPoolInteractor import jp.co.soramitsu.wallet.impl.domain.model.Asset import kotlinx.coroutines.Dispatchers @@ -26,18 +28,19 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigInteger import javax.inject.Inject -import jp.co.soramitsu.staking.impl.presentation.validators.toModel @HiltViewModel class SelectedValidatorsViewModel @Inject constructor( private val poolSharedStateProvider: StakingPoolSharedStateProvider, private val poolInteractor: StakingPoolInteractor, private val resourceManager: ResourceManager, - private val router: StakingRouter + private val router: StakingRouter, + private val stakingInteractor: StakingInteractor ) : BaseViewModel(), SelectedValidatorsInterface { private val validatorsToShow: List = poolSharedStateProvider.requireSelectedValidatorsState.selectedValidators @@ -102,7 +105,10 @@ class SelectedValidatorsViewModel @Inject constructor( mapValidatorToValidatorDetailsWithStakeFlagParcelModel(requireNotNull(nominatedValidator)) } - router.openValidatorDetails(payload) + stakingInteractor.validatorDetailsCache.update { prev -> + prev + (payload.accountIdHex to payload) + } + router.openValidatorDetails(payload.accountIdHex) } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/current/CurrentValidatorsViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/current/CurrentValidatorsViewModel.kt index defe322154..5d713813ae 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/current/CurrentValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/current/CurrentValidatorsViewModel.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -187,6 +188,9 @@ class CurrentValidatorsViewModel @Inject constructor( mapValidatorToValidatorDetailsWithStakeFlagParcelModel(nominatedValidator) } - router.openValidatorDetails(payload) + stakingInteractor.validatorDetailsCache.update { prev -> + prev + (payload.accountIdHex to payload) + } + router.openValidatorDetails(payload.accountIdHex) } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/details/ValidatorDetailsFragment.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/details/ValidatorDetailsFragment.kt index 47bbaaa683..0b4616784d 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/details/ValidatorDetailsFragment.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/validators/details/ValidatorDetailsFragment.kt @@ -12,7 +12,6 @@ import jp.co.soramitsu.common.utils.makeGone import jp.co.soramitsu.common.utils.makeVisible import jp.co.soramitsu.feature_staking_impl.R import jp.co.soramitsu.feature_staking_impl.databinding.FragmentValidatorDetailsBinding -import jp.co.soramitsu.staking.impl.presentation.validators.parcel.ValidatorDetailsParcelModel @AndroidEntryPoint class ValidatorDetailsFragment : BaseBottomSheetDialogFragment(R.layout.fragment_validator_details) { @@ -20,9 +19,9 @@ class ValidatorDetailsFragment : BaseBottomSheetDialogFragment(KEY_VALIDATOR)!! + private val validatorIdHex = savedStateHandle.get(KEY_VALIDATOR)!! + private val validator = requireNotNull(interactor.validatorDetailsCache.value[validatorIdHex]) private val assetFlow = interactor.currentAssetFlow() .share() diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt index 771ec6e928..cbfe74da31 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt @@ -325,8 +325,8 @@ class WalletFeatureModule { @Provides @Singleton - fun provideXcmService(): XcmService { - return XcmService() + fun provideXcmService(chainRegistry: ChainRegistry): XcmService { + return XcmService(chainRegistry) } @Provides diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/QuickInputsUseCaseImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/QuickInputsUseCaseImpl.kt index 6b328d11ee..5827d959eb 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/QuickInputsUseCaseImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/QuickInputsUseCaseImpl.kt @@ -1,8 +1,5 @@ package jp.co.soramitsu.wallet.impl.domain -import java.math.BigDecimal -import java.math.BigInteger -import java.math.RoundingMode import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository import jp.co.soramitsu.account.api.domain.model.accountId import jp.co.soramitsu.account.api.domain.model.address @@ -20,11 +17,14 @@ import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletRepository import jp.co.soramitsu.wallet.impl.domain.model.Transfer import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext +import java.math.BigDecimal +import java.math.BigInteger +import java.math.RoundingMode +import kotlin.coroutines.CoroutineContext private const val CROSS_CHAIN_ED_SAFE_TRANSFER_MULTIPLIER = 1.1 @@ -162,7 +162,10 @@ class QuickInputsUseCaseImpl( originNetworkId = originChainId, destinationNetworkId = destinationChainId, asset = asset.token.configuration, - amount = transferable * input.toBigDecimal() + destinationFee + amount = (transferable * input.toBigDecimal()).setScale( + chainAsset.precision, + RoundingMode.HALF_DOWN + ) + destinationFee ).takeIf { chainAsset.isUtility } ?: BigDecimal.ZERO val quickAmountWithoutExtraPays = diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 87571d6238..71c5ac81d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,7 +49,7 @@ retrofit = "2.9.0" roomVersion = "2.6.1" rules = "1.5.0" runner = "1.5.2" -sharedFeaturesVersion = "1.1.1.35-FLW" +sharedFeaturesVersion = "1.1.1.36-FLW" shimmerVersion = "0.5.0" sonarqubeGradlePlugin = "3.3" soraUiCore = "0.2.32"