From bbec2208e4ae664e18401ad4339e93549279d0ce Mon Sep 17 00:00:00 2001 From: arvifox Date: Tue, 17 Oct 2023 12:25:26 +0300 Subject: [PATCH] sa-234 minor fixes --- .../soramitsu/common/domain/RepeatStrategy.kt | 44 ++++ common/src/main/res/values-ru/strings.xml | 2 +- common/src/main/res/values-zh-rCN/strings.xml | 14 +- .../common/domain/RetryStrategyTests.kt | 94 +++++++ .../compose/states/PoolsListState.kt | 15 +- .../assetdetails/AssetDetailsBalanceCard.kt | 114 ++++----- .../assetdetails/AssetDetailsFragment.kt | 87 ++----- .../assetdetails/AssetDetailsScreen.kt | 236 ++++++++++++++++++ .../data/BlockExplorerManager.kt | 24 +- .../domain/EcoSystemTokensInteractor.kt | 15 +- .../domain/PoolsUpdateSubscription.kt | 7 + .../presentation/EcoSystemTokensState.kt | 2 +- .../start/StartScreenViewModel.kt | 3 +- 13 files changed, 503 insertions(+), 154 deletions(-) create mode 100644 common/src/test/java/jp/co/soramitsu/common/domain/RetryStrategyTests.kt create mode 100644 feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsScreen.kt diff --git a/common/src/main/java/jp/co/soramitsu/common/domain/RepeatStrategy.kt b/common/src/main/java/jp/co/soramitsu/common/domain/RepeatStrategy.kt index 04a82401e..8d943491c 100644 --- a/common/src/main/java/jp/co/soramitsu/common/domain/RepeatStrategy.kt +++ b/common/src/main/java/jp/co/soramitsu/common/domain/RepeatStrategy.kt @@ -32,14 +32,29 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package jp.co.soramitsu.common.domain +import kotlinx.coroutines.delay + interface RepeatStrategy { suspend fun repeat(block: suspend () -> Unit) } +interface RetryStrategy { + @Throws(RuntimeException::class) + suspend fun retryIf( + retries: Int, + predicate: suspend (cause: Throwable) -> Boolean, + block: suspend () -> T, + ): T +} + object RepeatStrategyBuilder { fun infinite(): RepeatStrategy = InfiniteRepeatStrategy() } +object RetryStrategyBuilder { + fun build(): RetryStrategy = RetryStrategyImpl() +} + private class InfiniteRepeatStrategy : RepeatStrategy { override suspend fun repeat(block: suspend () -> Unit) { while (true) { @@ -47,3 +62,32 @@ private class InfiniteRepeatStrategy : RepeatStrategy { } } } + +private class RetryStrategyImpl : RetryStrategy { + + override suspend fun retryIf( + retries: Int, + predicate: suspend (cause: Throwable) -> Boolean, + block: suspend () -> T, + ): T { + var attempt = 0 + var shallRetry: Boolean + do { + shallRetry = false + runCatching { + block.invoke() + }.onSuccess { + return it + }.onFailure { t -> + if (predicate(t) && attempt < retries) { + shallRetry = true + attempt++ + delay(500) + } else { + throw t + } + } + } while (shallRetry) + throw RuntimeException("RetryStrategy") + } +} diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index fe7d0d098..8159f082c 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -234,7 +234,7 @@ Режим редактирования Адрес получателя будет отображен здесь. Получатели появятся здесь - Ввдите пароль резервной копии + Введите пароль резервной копии Введите пароль резервной копии для выбранного аккаунта Неверный пароль Введите пароль diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml index 6fb57ff37..24c69ab45 100644 --- a/common/src/main/res/values-zh-rCN/strings.xml +++ b/common/src/main/res/values-zh-rCN/strings.xml @@ -41,7 +41,7 @@ 待发送的资产 备份已存在。继续覆盖它。 备份失败。您的Google Drive空间不足,请释放一些空间并重试。 - Backup now + 立即备份 密码的强制要求已满足 至少六个字符。为了更好的安全性,可以包含大写字母、数字和非字母字符。 设置密码将加密您的Google备份。在恢复钱包时,您需要输入此密码。\n最小密码长度为6个字符。 @@ -51,7 +51,7 @@ 购买XOR代币 用欧元购买或出售XOR代币 用法币购买XOR - Can\'t load balance + 无法加载余额 更改语言 更改PIN码 选择代币 @@ -238,7 +238,7 @@ 创建池子 提供流动性并获得奖励 在SORA网络上进行代币兑换 - 如果您无法访问此设备,您的资金将会丢失,除非您备份! + 如果你无法访问这台设备,除非你进行备份,否则你的资金将会丢失! 备份选项 显示密码短语 显示原始种子 @@ -386,7 +386,7 @@ %s 秒剩余 结束%s - Protect yourself from losing access to your funds + 保护自己免于失去对资金的访问权限 QR 代码 从相册 接收代币 @@ -491,7 +491,7 @@ IBAN账户详情 登录或注册 没有更多的免费尝试 - 退出 SORA 卡 + 退出SORA卡 您即将退出 SORA Card。您仍然可以使用 SORA Card 独立应用程序,但余额将不再在 SORA 钱包中可用。 推荐并赚取 推荐SORA卡。\n获得20欧元奖金。 @@ -512,7 +512,7 @@ 成功的 状态 您已成功导入一个账户 - Support chat + 支持聊天 确保在此事务之后还剩下 %s。否则,您将无法进行其他交易。 切换节点 系统外观 @@ -530,7 +530,7 @@ 发生了意外错误。\n\n您可以在SORA社区的@SORAhappiness Telegram聊天中解决此问题。 SORA 应用程序的更新已下载并可以安装。 \n\n使用最新版本的应用程序很重要。 \n\n为了更新应用程序,需要重新启动。 您想现在安装更新吗? - Wallet backup + 钱包备份 已被锁定 已保留 已绑定 diff --git a/common/src/test/java/jp/co/soramitsu/common/domain/RetryStrategyTests.kt b/common/src/test/java/jp/co/soramitsu/common/domain/RetryStrategyTests.kt new file mode 100644 index 000000000..98955a419 --- /dev/null +++ b/common/src/test/java/jp/co/soramitsu/common/domain/RetryStrategyTests.kt @@ -0,0 +1,94 @@ +/* +This file is part of the SORA network and Polkaswap app. + +Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +SPDX-License-Identifier: BSD-4-Clause + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or other +materials provided with the distribution. + +All advertising materials mentioning features or use of this software must display +the following acknowledgement: This product includes software developed by Polka Biome +Ltd., SORA, and Polkaswap. + +Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jp.co.soramitsu.common.domain + +import jp.co.soramitsu.test_shared.MainCoroutineRule +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class RetryStrategyTests { + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + @Test + fun `retry test 01`() = runTest { + val retry = RetryStrategyBuilder.build() + val res = retry.retryIf( + 3, + { t -> t is IllegalArgumentException }, + { okTime1() }, + ) + assertEquals(12, res) + } + + @Test + fun `retry test 02`() = runTest { + val retry = RetryStrategyBuilder.build() + val res = retry.retryIf( + 3, + { t -> t is IllegalArgumentException }, + { okTime2() }, + ) + assertEquals(2, res) + } + + @Test(expected = IllegalArgumentException::class) + fun `retry test 03`() = runTest { + val retry = RetryStrategyBuilder.build() + retry.retryIf( + 3, + { t -> t is IllegalArgumentException }, + { okTime3() }, + ) + } + + private fun okTime1(): Int { + return 12 + } + + private var count2 = 0 + private fun okTime2(): Int { + count2++ + if (count2 == 1) throw IllegalArgumentException("count2=$count2") + return count2 + } + + private var count3 = 0 + private fun okTime3(): Int { + count3++ + if (count3 < 5) throw IllegalArgumentException("count3=$count3") + return count3 + } +} \ No newline at end of file diff --git a/common_wallet/src/main/java/jp/co/soramitsu/common_wallet/presentation/compose/states/PoolsListState.kt b/common_wallet/src/main/java/jp/co/soramitsu/common_wallet/presentation/compose/states/PoolsListState.kt index 739dfdd8c..d00379d69 100644 --- a/common_wallet/src/main/java/jp/co/soramitsu/common_wallet/presentation/compose/states/PoolsListState.kt +++ b/common_wallet/src/main/java/jp/co/soramitsu/common_wallet/presentation/compose/states/PoolsListState.kt @@ -34,11 +34,9 @@ package jp.co.soramitsu.common_wallet.presentation.compose.states import jp.co.soramitsu.common.domain.AssetHolder import jp.co.soramitsu.common.domain.formatFiat -import jp.co.soramitsu.common.domain.formatFiatChange import jp.co.soramitsu.common.domain.iconUri import jp.co.soramitsu.common.util.NumbersFormatter import jp.co.soramitsu.common.util.StringPair -import jp.co.soramitsu.common.util.ext.isNanZero import jp.co.soramitsu.common_wallet.domain.model.CommonUserPoolData class PoolsListState( @@ -86,12 +84,13 @@ fun mapPoolsData( token2Icon = poolData.basic.targetToken.iconUri(), fiat = formatted[i]?.first?.let { poolData.basic.baseToken.formatFiat(it, numbersFormatter) } .orEmpty(), - fiatChange = formatted[i]?.second?.let { - formatFiatChange( - it.isNanZero(), - numbersFormatter - ) - }.orEmpty(), + fiatChange = "", +// fiatChange = formatted[i]?.second?.let { +// formatFiatChange( +// it.isNanZero(), +// numbersFormatter +// ) +// }.orEmpty(), tokenIds = poolData.basic.baseToken.id to poolData.basic.targetToken.id, ) } diff --git a/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/components/compose/assetdetails/AssetDetailsBalanceCard.kt b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/components/compose/assetdetails/AssetDetailsBalanceCard.kt index 8b0b116ff..2d2f64612 100644 --- a/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/components/compose/assetdetails/AssetDetailsBalanceCard.kt +++ b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/components/compose/assetdetails/AssetDetailsBalanceCard.kt @@ -74,7 +74,6 @@ internal fun AssetDetailsBalanceCard( frozenAmount: String? = null, frozenAmountFiat: String? = null, isTransferableAmountAvailable: Boolean = false, - hasHistory: Boolean = false, buyCryptoAvailable: Boolean = false, onSendClick: () -> Unit, onReceiveClick: () -> Unit, @@ -96,20 +95,59 @@ internal fun AssetDetailsBalanceCard( .fillMaxWidth() .wrapContentHeight() ) { - if (hasHistory) { + Row( + modifier = Modifier + .padding(horizontal = Dimens.x3) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(id = R.string.asset_details_liquid_balance), + color = MaterialTheme.customColors.fgPrimary, + style = MaterialTheme.customTypography.headline2 + ) + Text( + text = amountFiat, + color = MaterialTheme.customColors.fgPrimary, + style = MaterialTheme.customTypography.headline2 + ) + } + Text( + modifier = Modifier + .padding(horizontal = Dimens.x3) + .fillMaxWidth(), + text = amount, + style = MaterialTheme.customTypography.textXSBold, + color = MaterialTheme.customColors.fgSecondary + ) + + Spacer(modifier = Modifier.height(Dimens.x2)) + + if (frozenAmount != null && frozenAmountFiat != null) { Row( modifier = Modifier .padding(horizontal = Dimens.x3) .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { + Row { + Text( + text = stringResource(id = R.string.details_frozen), + style = MaterialTheme.customTypography.headline2, + color = MaterialTheme.customColors.fgSecondary + ) + Icon( + modifier = Modifier + .padding(start = Dimens.x1) + .align(Alignment.CenterVertically) + .size(Dimens.x2), + painter = painterResource(id = R.drawable.ic_neu_lock), + tint = MaterialTheme.customColors.fgSecondary, + contentDescription = "", + ) + } Text( - text = stringResource(id = R.string.asset_details_liquid_balance), - color = MaterialTheme.customColors.fgPrimary, - style = MaterialTheme.customTypography.headline2 - ) - Text( - text = amountFiat, + text = frozenAmountFiat, color = MaterialTheme.customColors.fgPrimary, style = MaterialTheme.customTypography.headline2 ) @@ -118,60 +156,19 @@ internal fun AssetDetailsBalanceCard( modifier = Modifier .padding(horizontal = Dimens.x3) .fillMaxWidth(), - text = amount, + text = frozenAmount, style = MaterialTheme.customTypography.textXSBold, color = MaterialTheme.customColors.fgSecondary ) - - Spacer(modifier = Modifier.height(Dimens.x2)) - - if (frozenAmount != null && frozenAmountFiat != null) { - Row( - modifier = Modifier - .padding(horizontal = Dimens.x3) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row { - Text( - text = stringResource(id = R.string.details_frozen), - style = MaterialTheme.customTypography.headline2, - color = MaterialTheme.customColors.fgSecondary - ) - Icon( - modifier = Modifier - .padding(start = Dimens.x1) - .align(Alignment.CenterVertically) - .size(Dimens.x2), - painter = painterResource(id = R.drawable.ic_neu_lock), - tint = MaterialTheme.customColors.fgSecondary, - contentDescription = "", - ) - } - Text( - text = frozenAmountFiat, - color = MaterialTheme.customColors.fgPrimary, - style = MaterialTheme.customTypography.headline2 - ) - } - Text( - modifier = Modifier - .padding(horizontal = Dimens.x3) - .fillMaxWidth(), - text = frozenAmount, - style = MaterialTheme.customTypography.textXSBold, - color = MaterialTheme.customColors.fgSecondary - ) - } - Divider( - color = MaterialTheme.customColors.fgOutline, - thickness = 1.dp, - modifier = Modifier - .padding(horizontal = Dimens.x3, vertical = Dimens.x2) - .height(1.dp) - .fillMaxWidth() - ) } + Divider( + color = MaterialTheme.customColors.fgOutline, + thickness = 1.dp, + modifier = Modifier + .padding(horizontal = Dimens.x3, vertical = Dimens.x2) + .height(1.dp) + .fillMaxWidth() + ) Row( modifier = Modifier .wrapContentHeight() @@ -255,7 +252,6 @@ private fun PreviewAssetDetailsBalanceCard() { frozenAmount = "12.3 XOR", frozenAmountFiat = "$ 1000.1", buyCryptoAvailable = true, - hasHistory = true, isTransferableAmountAvailable = true, onSendClick = { }, onReceiveClick = { }, diff --git a/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsFragment.kt b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsFragment.kt index f8c4541cc..6c1844ce6 100644 --- a/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsFragment.kt +++ b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsFragment.kt @@ -34,15 +34,10 @@ package jp.co.soramitsu.feature_assets_impl.presentation.screens.assetdetails import android.os.Bundle import android.view.View -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -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.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -59,11 +54,6 @@ import jp.co.soramitsu.common.base.SoraBaseFragment import jp.co.soramitsu.common.base.theOnlyRoute import jp.co.soramitsu.common.domain.BottomBarController import jp.co.soramitsu.core_di.viewmodel.CustomViewModelFactory -import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsBalanceCard -import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsPooledCard -import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsRecentActivityCard -import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsTokenPriceCard -import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetIdCard import jp.co.soramitsu.ui_core.resources.Dimens @AndroidEntryPoint @@ -90,7 +80,7 @@ class AssetDetailsFragment : SoraBaseFragment() { } } - @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) + @OptIn(ExperimentalMaterialApi::class) override fun NavGraphBuilder.content( scrollState: ScrollState, navController: NavHostController @@ -110,63 +100,24 @@ class AssetDetailsFragment : SoraBaseFragment() { .padding(horizontal = Dimens.x2) .pullRefresh(pullRefresh, true) ) { - if (stateData.xorBalance != null) { - XorBalancesDialog( - state = stateData.xorBalance, - onClick = viewModel::onBalanceClick - ) - } - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - ) { - AssetDetailsTokenPriceCard( - tokenName = stateData.tokenName, - tokenSymbol = stateData.tokenSymbol, - tokenPrice = stateData.price, - tokenPriceChange = stateData.priceChange, - iconUri = stateData.tokenIcon, - ) - Spacer(modifier = Modifier.size(Dimens.x2)) - AssetDetailsBalanceCard( - amount = stateData.transferableBalance, - amountFiat = stateData.transferableBalanceFiat, - frozenAmount = stateData.frozenBalance, - frozenAmountFiat = stateData.frozenBalanceFiat, - buyCryptoAvailable = stateData.buyCryptoAvailable, - isTransferableAmountAvailable = stateData.isTransferableBalanceAvailable, - hasHistory = stateData.events.isNotEmpty(), - onSendClick = viewModel::sendClicked, - onReceiveClick = viewModel::receiveClicked, - onSwapClick = viewModel::swapClicked, - onBalanceClick = viewModel::onBalanceClick, - onBuyCryptoClick = viewModel::onBuyCrypto - ) - if (stateData.poolsState.pools.isNotEmpty()) { - Spacer(modifier = Modifier.size(Dimens.x2)) - AssetDetailsPooledCard( - title = stateData.poolsCardTitle, - state = stateData.poolsState, - onPoolClick = viewModel::onPoolClick - ) - } - if (stateData.events.isNotEmpty()) { - Spacer(modifier = Modifier.size(Dimens.x2)) - AssetDetailsRecentActivityCard( - events = stateData.events, - onShowMoreActivity = viewModel::onRecentClick, - onHistoryItemClick = viewModel::onHistoryItemClick - ) - } - Spacer(modifier = Modifier.size(Dimens.x2)) - AssetIdCard( - id = stateData.tokenId, - onClick = viewModel::onAssetIdClick - ) - Spacer(modifier = Modifier.size(Dimens.x2)) - } - PullRefreshIndicator(assetState.loading, pullRefresh, Modifier.align(Alignment.TopCenter)) + AssetDetailsScreen( + stateData = stateData, + scrollState = scrollState, + onBalanceClick = viewModel::onBalanceClick, + onSendClick = viewModel::sendClicked, + onReceiveClick = viewModel::receiveClicked, + onSwapClick = viewModel::swapClicked, + onBuyCrypto = viewModel::onBuyCrypto, + onPoolClick = viewModel::onPoolClick, + onRecentClick = viewModel::onRecentClick, + onHistoryItemClick = viewModel::onHistoryItemClick, + onAssetIdClick = viewModel::onAssetIdClick, + ) + PullRefreshIndicator( + assetState.loading, + pullRefresh, + Modifier.align(Alignment.TopCenter) + ) } } } diff --git a/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsScreen.kt b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsScreen.kt new file mode 100644 index 000000000..828988ccb --- /dev/null +++ b/feature_assets_impl/src/main/java/jp/co/soramitsu/feature_assets_impl/presentation/screens/assetdetails/AssetDetailsScreen.kt @@ -0,0 +1,236 @@ +/* +This file is part of the SORA network and Polkaswap app. + +Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +SPDX-License-Identifier: BSD-4-Clause + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or other +materials provided with the distribution. + +All advertising materials mentioning features or use of this software must display +the following acknowledgement: This product includes software developed by Polka Biome +Ltd., SORA, and Polkaswap. + +Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jp.co.soramitsu.feature_assets_impl.presentation.screens.assetdetails + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import jp.co.soramitsu.common.domain.DEFAULT_ICON_URI +import jp.co.soramitsu.common.util.StringPair +import jp.co.soramitsu.common_wallet.presentation.compose.states.PoolsListItemState +import jp.co.soramitsu.common_wallet.presentation.compose.states.PoolsListState +import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsBalanceCard +import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsPooledCard +import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsRecentActivityCard +import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetDetailsTokenPriceCard +import jp.co.soramitsu.feature_assets_impl.presentation.components.compose.assetdetails.AssetIdCard +import jp.co.soramitsu.feature_assets_impl.presentation.states.AssetCardStateData +import jp.co.soramitsu.feature_blockexplorer_api.presentation.txhistory.EventUiModel +import jp.co.soramitsu.feature_blockexplorer_api.presentation.txhistory.TransactionStatus +import jp.co.soramitsu.ui_core.resources.Dimens + +@Composable +internal fun AssetDetailsScreen( + stateData: AssetCardStateData, + scrollState: ScrollState, + onBalanceClick: () -> Unit, + onSendClick: () -> Unit, + onReceiveClick: () -> Unit, + onSwapClick: () -> Unit, + onBuyCrypto: () -> Unit, + onPoolClick: (StringPair) -> Unit, + onRecentClick: () -> Unit, + onHistoryItemClick: (String) -> Unit, + onAssetIdClick: () -> Unit, +) { + if (stateData.xorBalance != null) { + XorBalancesDialog( + state = stateData.xorBalance, + onClick = onBalanceClick, + ) + } + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + ) { + AssetDetailsTokenPriceCard( + tokenName = stateData.tokenName, + tokenSymbol = stateData.tokenSymbol, + tokenPrice = stateData.price, + tokenPriceChange = stateData.priceChange, + iconUri = stateData.tokenIcon, + ) + Spacer(modifier = Modifier.size(Dimens.x2)) + AssetDetailsBalanceCard( + amount = stateData.transferableBalance, + amountFiat = stateData.transferableBalanceFiat, + frozenAmount = stateData.frozenBalance, + frozenAmountFiat = stateData.frozenBalanceFiat, + buyCryptoAvailable = stateData.buyCryptoAvailable, + isTransferableAmountAvailable = stateData.isTransferableBalanceAvailable, + onSendClick = onSendClick, + onReceiveClick = onReceiveClick, + onSwapClick = onSwapClick, + onBalanceClick = onBalanceClick, + onBuyCryptoClick = onBuyCrypto, + ) + if (stateData.poolsState.pools.isNotEmpty()) { + Spacer(modifier = Modifier.size(Dimens.x2)) + AssetDetailsPooledCard( + title = stateData.poolsCardTitle, + state = stateData.poolsState, + onPoolClick = onPoolClick, + ) + } + if (stateData.events.isNotEmpty()) { + Spacer(modifier = Modifier.size(Dimens.x2)) + AssetDetailsRecentActivityCard( + events = stateData.events, + onShowMoreActivity = onRecentClick, + onHistoryItemClick = onHistoryItemClick, + ) + } + Spacer(modifier = Modifier.size(Dimens.x2)) + AssetIdCard( + id = stateData.tokenId, + onClick = onAssetIdClick, + ) + Spacer(modifier = Modifier.size(Dimens.x2)) + } +} + +@Composable +@Preview +private fun PreviewAssetDetailsScreen01() { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + AssetDetailsScreen( + AssetCardStateData( + "tokenId", + "tokenName", + DEFAULT_ICON_URI, + "XOR", + "$12.3", + "+4%", + "12314324.13", + "$879.12", + "123.123", + "$99.9", + null, + "title", + PoolsListState( + listOf( + PoolsListItemState( + token1Icon = DEFAULT_ICON_URI, + token2Icon = DEFAULT_ICON_URI, + poolAmounts = "123.456", + poolName = "XOR - VAL", + fiat = "$7908", + fiatChange = "+23.1 %", + tokenIds = "" to "", + ), + ) + ), + "456", + true, + true, + emptyList(), + true, + ), + rememberScrollState(), + {}, {}, {}, {}, {}, {}, {}, {}, {} + ) + } +} + +@Composable +@Preview +private fun PreviewAssetDetailsScreen02() { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + AssetDetailsScreen( + AssetCardStateData( + "tokenId", + "tokenName", + DEFAULT_ICON_URI, + "XOR", + "$12.3", + "+4%", + "12314324.13", + "$879.12", + "123.123", + "$99.9", + null, + "title", + PoolsListState( + listOf( + PoolsListItemState( + token1Icon = DEFAULT_ICON_URI, + token2Icon = DEFAULT_ICON_URI, + poolAmounts = "123.456", + poolName = "XOR - VAL", + fiat = "$7908", + fiatChange = "+23.1 %", + tokenIds = "" to "", + ), + ) + ), + "456", + true, + true, + listOf( + EventUiModel.EventTxUiModel.EventTransferInUiModel( + "hash", + DEFAULT_ICON_URI, + "address", + "12.12.1970", + 123123123, + "123.456", + "$78.23", + TransactionStatus.COMMITTED, + ) + ), + true, + ), + rememberScrollState(), + {}, {}, {}, {}, {}, {}, {}, {}, {} + ) + } +} diff --git a/feature_blockexplorer_api/src/main/java/jp/co/soramitsu/feature_blockexplorer_api/data/BlockExplorerManager.kt b/feature_blockexplorer_api/src/main/java/jp/co/soramitsu/feature_blockexplorer_api/data/BlockExplorerManager.kt index d11aeaf4b..a63f05f3f 100644 --- a/feature_blockexplorer_api/src/main/java/jp/co/soramitsu/feature_blockexplorer_api/data/BlockExplorerManager.kt +++ b/feature_blockexplorer_api/src/main/java/jp/co/soramitsu/feature_blockexplorer_api/data/BlockExplorerManager.kt @@ -39,6 +39,7 @@ import javax.inject.Inject import javax.inject.Singleton import jp.co.soramitsu.common.config.BuildConfigWrapper import jp.co.soramitsu.common.domain.AppStateProvider +import jp.co.soramitsu.common.domain.RetryStrategyBuilder import jp.co.soramitsu.common.domain.fiatChange import jp.co.soramitsu.common.logger.FirebaseWrapper import jp.co.soramitsu.core_db.AppDatabase @@ -46,6 +47,7 @@ import jp.co.soramitsu.core_db.model.FiatTokenPriceLocal import jp.co.soramitsu.core_db.model.ReferralLocal import jp.co.soramitsu.xnetworking.basic.common.Utils.toDoubleNan import jp.co.soramitsu.xnetworking.basic.networkclient.SoramitsuNetworkClient +import jp.co.soramitsu.xnetworking.basic.networkclient.SoramitsuNetworkException import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.SoraWalletBlockExplorerInfo import jp.co.soramitsu.xnetworking.sorawallet.blockexplorerinfo.sbapy.SbApyInfo import kotlinx.coroutines.sync.Mutex @@ -73,22 +75,33 @@ class BlockExplorerManager @Inject constructor( }?.sbApy?.times(100) suspend fun getTokensLiquidity(tokenIds: List): List> = - assetsInfo ?: getAssetsInfoInternal(tokenIds).also { - assetsInfo = it + assetsInfo ?: mutex.withLock { + assetsInfo ?: getAssetsInfoInternal(tokenIds).also { + assetsInfo = it + } } - private suspend fun getAssetsInfoInternal(tokenIds: List): List> = mutex.withLock { + private suspend fun getAssetsInfoInternal(tokenIds: List): List> = runCatching { val selected = soraConfigManager.getSelectedCurrency() val tokens = db.assetDao().getFiatTokenPriceLocal(selected.code) val yesterdayHour = yesterday() val resultList = mutableListOf>() val fiats = mutableListOf() - info.getAssetsInfo(tokenIds, yesterdayHour).forEach { assetInfo -> + RetryStrategyBuilder.build().retryIf( + retries = 3, + predicate = { t -> t is SoramitsuNetworkException }, + block = { info.getAssetsInfo(tokenIds, yesterdayHour) }, + ).forEach { assetInfo -> val dbValue = tokens.find { it.tokenIdFiat == assetInfo.tokenId } val delta = assetInfo.hourDelta if (dbValue != null && delta != null) { - fiats.add(dbValue.copy(fiatPricePrevH = delta, fiatPricePrevHTime = yesterdayHour)) + fiats.add( + dbValue.copy( + fiatPricePrevH = delta, + fiatPricePrevHTime = yesterdayHour + ) + ) } resultList.add(assetInfo.tokenId to BigInteger(assetInfo.liquidity)) } @@ -98,7 +111,6 @@ class BlockExplorerManager @Inject constructor( FirebaseWrapper.recordException(it) emptyList() } - } suspend fun updatePoolsSbApy() { updateSbApyInternal() diff --git a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/EcoSystemTokensInteractor.kt b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/EcoSystemTokensInteractor.kt index ecd820e51..7c85015dc 100644 --- a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/EcoSystemTokensInteractor.kt +++ b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/EcoSystemTokensInteractor.kt @@ -32,13 +32,17 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package jp.co.soramitsu.feature_ecosystem_impl.domain +import java.math.BigInteger import jp.co.soramitsu.common.util.ext.compareNullDesc import jp.co.soramitsu.common.util.ext.multiplyNullable import jp.co.soramitsu.common.util.mapBalance import jp.co.soramitsu.feature_assets_api.data.AssetsRepository import jp.co.soramitsu.feature_blockexplorer_api.data.BlockExplorerManager import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart internal interface EcoSystemTokensInteractor { fun subscribeTokens(): Flow @@ -49,8 +53,9 @@ internal class EcoSystemTokensInteractorImpl( private val blockExplorerManager: BlockExplorerManager, ) : EcoSystemTokensInteractor { override fun subscribeTokens(): Flow { - return assetsRepository.subscribeTokensList().map { list -> - val liquidity = blockExplorerManager.getTokensLiquidity(list.map { it.id }) + return assetsRepository.subscribeTokensList().combine(flowTokensLiquidity) { f1, f2 -> + f1 to f2 + }.map { (list, liquidity) -> val mapped = list.map { token -> token to liquidity.firstOrNull { it.first == token.id }?.second?.let { bi -> mapBalance(bi, token.precision) @@ -69,4 +74,10 @@ internal class EcoSystemTokensInteractorImpl( ) } } + + private val flowTokensLiquidity: Flow>> = flow { + emit(blockExplorerManager.getTokensLiquidity(assetsRepository.tokensList().map { it.id })) + }.onStart { + emit(emptyList()) + } } diff --git a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/PoolsUpdateSubscription.kt b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/PoolsUpdateSubscription.kt index 73c604249..91516684d 100644 --- a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/PoolsUpdateSubscription.kt +++ b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/domain/PoolsUpdateSubscription.kt @@ -37,9 +37,11 @@ import jp.co.soramitsu.feature_polkaswap_api.domain.interfaces.PolkaswapSubscrip import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.withContext internal interface PoolsUpdateSubscription { fun start(): Flow + suspend fun updateBasicPools() } internal class PoolsUpdateSubscriptionImpl( @@ -54,4 +56,9 @@ internal class PoolsUpdateSubscriptionImpl( repository.updateBasicPools() } .flowOn(coroutineManager.io) + + override suspend fun updateBasicPools() = + withContext(coroutineManager.io) { + repository.updateBasicPools() + } } diff --git a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/EcoSystemTokensState.kt b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/EcoSystemTokensState.kt index afe997e93..95be37fa9 100644 --- a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/EcoSystemTokensState.kt +++ b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/EcoSystemTokensState.kt @@ -50,5 +50,5 @@ internal data class EcoSystemPoolsState( ) internal val initialEcoSystemPoolsState = EcoSystemPoolsState( - pools = List(5) { i -> basicPoolListItemStateEmpty }, + pools = List(5) { _ -> basicPoolListItemStateEmpty }, ) diff --git a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/start/StartScreenViewModel.kt b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/start/StartScreenViewModel.kt index eb09f3137..04dc3b865 100644 --- a/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/start/StartScreenViewModel.kt +++ b/feature_ecosystem_impl/src/main/java/jp/co/soramitsu/feature_ecosystem_impl/presentation/start/StartScreenViewModel.kt @@ -49,7 +49,6 @@ import jp.co.soramitsu.feature_ecosystem_impl.presentation.initialEcoSystemPools import jp.co.soramitsu.feature_ecosystem_impl.presentation.initialEcoSystemTokensState import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -66,7 +65,7 @@ internal class StartScreenViewModel @Inject constructor( init { viewModelScope.launch { - poolsUpdateSubscription.start().collect() + poolsUpdateSubscription.updateBasicPools() } }