diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt
index 4c2e3b2da5..6abdb481f5 100644
--- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt
+++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt
@@ -5,6 +5,7 @@ import dagger.Provides
import io.novafoundation.nova.app.root.navigation.NavigationHolder
import io.novafoundation.nova.app.root.navigation.Navigator
import io.novafoundation.nova.app.root.navigation.settings.SettingsNavigator
+import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_settings_impl.SettingsRouter
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
@@ -15,8 +16,9 @@ class SettingsNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
+ rootRouter: RootRouter,
navigationHolder: NavigationHolder,
walletConnectRouter: WalletConnectRouter,
navigator: Navigator,
- ): SettingsRouter = SettingsNavigator(navigationHolder, walletConnectRouter, navigator)
+ ): SettingsRouter = SettingsNavigator(navigationHolder, rootRouter, walletConnectRouter, navigator)
}
diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt
index c08822e2a5..b31933209d 100644
--- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt
+++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt
@@ -52,14 +52,18 @@ import io.novafoundation.nova.feature_account_impl.presentation.startCreateWalle
import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.change.ChangeWatchAccountFragment
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment
+import io.novafoundation.nova.feature_assets.presentation.flow.network.NetworkFlowFragment
+import io.novafoundation.nova.feature_assets.presentation.flow.network.NetworkFlowPayload
import io.novafoundation.nova.feature_assets.presentation.model.OperationParcelizeModel
import io.novafoundation.nova.feature_assets.presentation.receive.ReceiveFragment
import io.novafoundation.nova.feature_assets.presentation.send.TransferDraft
import io.novafoundation.nova.feature_assets.presentation.send.amount.SelectSendFragment
import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload
import io.novafoundation.nova.feature_assets.presentation.send.confirm.ConfirmSendFragment
-import io.novafoundation.nova.feature_assets.presentation.swap.AssetSwapFlowFragment
-import io.novafoundation.nova.feature_assets.presentation.swap.SwapFlowPayload
+import io.novafoundation.nova.feature_assets.presentation.swap.asset.AssetSwapFlowFragment
+import io.novafoundation.nova.feature_assets.presentation.swap.asset.SwapFlowPayload
+import io.novafoundation.nova.feature_assets.presentation.swap.network.NetworkSwapFlowFragment
+import io.novafoundation.nova.feature_assets.presentation.swap.network.NetworkSwapFlowPayload
import io.novafoundation.nova.feature_assets.presentation.tokens.add.enterInfo.AddTokenEnterInfoFragment
import io.novafoundation.nova.feature_assets.presentation.tokens.add.enterInfo.AddTokenEnterInfoPayload
import io.novafoundation.nova.feature_assets.presentation.tokens.manage.chain.ManageChainTokensFragment
@@ -323,10 +327,6 @@ class Navigator(
navController?.navigate(R.id.action_open_receive, ReceiveFragment.getBundle(assetPayload))
}
- override fun openAssetFilters() {
- navController?.navigate(R.id.action_mainFragment_to_assetFiltersFragment)
- }
-
override fun openAssetSearch() {
navController?.navigate(R.id.action_mainFragment_to_assetSearchFragment)
}
@@ -387,6 +387,26 @@ class Navigator(
navController?.navigate(R.id.action_close_send_flow)
}
+ override fun openSendNetworks(payload: NetworkFlowPayload) {
+ navController?.navigate(R.id.action_sendFlow_to_sendFlowNetwork, NetworkFlowFragment.createPayload(payload))
+ }
+
+ override fun openReceiveNetworks(payload: NetworkFlowPayload) {
+ navController?.navigate(R.id.action_receiveFlow_to_receiveFlowNetwork, NetworkFlowFragment.createPayload(payload))
+ }
+
+ override fun openSwapNetworks(payload: NetworkSwapFlowPayload) {
+ navController?.navigate(R.id.action_selectAssetSwapFlowFragment_to_swapFlowNetworkFragment, NetworkSwapFlowFragment.createPayload(payload))
+ }
+
+ override fun openBuyNetworks(payload: NetworkFlowPayload) {
+ navController?.navigate(R.id.action_buyFlow_to_buyFlowNetwork, NetworkFlowFragment.createPayload(payload))
+ }
+
+ override fun returnToMainSwapScreen() {
+ navController?.navigate(R.id.action_return_to_swap_settings)
+ }
+
override fun openSwapFlow() {
val payload = SwapFlowPayload.InitialSelecting
navController?.navigate(R.id.action_mainFragment_to_swapFlow, AssetSwapFlowFragment.getBundle(payload))
diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt
index 8291139690..92a5ac4d00 100644
--- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt
+++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt
@@ -4,6 +4,7 @@ import io.novafoundation.nova.app.R
import io.novafoundation.nova.app.root.navigation.BaseNavigator
import io.novafoundation.nova.app.root.navigation.NavigationHolder
import io.novafoundation.nova.app.root.navigation.Navigator
+import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.feature_account_impl.presentation.pincode.PinCodeAction
import io.novafoundation.nova.feature_account_impl.presentation.pincode.PincodeFragment
import io.novafoundation.nova.feature_settings_impl.SettingsRouter
@@ -19,11 +20,16 @@ import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.
class SettingsNavigator(
navigationHolder: NavigationHolder,
+ private val rootRouter: RootRouter,
private val walletConnectDelegate: WalletConnectRouter,
private val delegate: Navigator
) : BaseNavigator(navigationHolder),
SettingsRouter {
+ override fun returnToWallet() {
+ rootRouter.returnToWallet()
+ }
+
override fun openWallets() {
delegate.openWallets()
}
@@ -80,6 +86,8 @@ class SettingsNavigator(
override fun openLanguages() = performNavigation(R.id.action_mainFragment_to_languagesFragment)
+ override fun openAppearance() = performNavigation(R.id.action_mainFragment_to_appearanceFragment)
+
override fun openChangePinCode() = performNavigation(
actionId = R.id.action_change_pin_code,
args = PincodeFragment.getPinCodeBundle(PinCodeAction.Change)
diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt
index 99cbbb0e24..ba72cd2dae 100644
--- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt
+++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt
@@ -6,8 +6,8 @@ import io.novafoundation.nova.app.root.navigation.NavigationHolder
import io.novafoundation.nova.app.root.navigation.Navigator
import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload
import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment
-import io.novafoundation.nova.feature_assets.presentation.swap.AssetSwapFlowFragment
-import io.novafoundation.nova.feature_assets.presentation.swap.SwapFlowPayload
+import io.novafoundation.nova.feature_assets.presentation.swap.asset.AssetSwapFlowFragment
+import io.novafoundation.nova.feature_assets.presentation.swap.asset.SwapFlowPayload
import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter
import io.novafoundation.nova.feature_swap_impl.presentation.confirmation.SwapConfirmationFragment
import io.novafoundation.nova.feature_swap_impl.presentation.confirmation.payload.SwapConfirmationPayload
@@ -33,12 +33,12 @@ class SwapNavigator(
override fun selectAssetIn(selectedAsset: AssetPayload?) {
val payload = SwapFlowPayload.ReselectAssetIn(selectedAsset)
- navigationHolder.navController?.navigate(R.id.action_swapMainSettingsFragment_to_swapFlow, AssetSwapFlowFragment.getBundle(payload))
+ navigationHolder.navController?.navigate(R.id.action_swapSettingsFragment_to_select_swap_token_graph, AssetSwapFlowFragment.getBundle(payload))
}
override fun selectAssetOut(selectedAsset: AssetPayload?) {
val payload = SwapFlowPayload.ReselectAssetOut(selectedAsset)
- navigationHolder.navController?.navigate(R.id.action_swapMainSettingsFragment_to_swapFlow, AssetSwapFlowFragment.getBundle(payload))
+ navigationHolder.navController?.navigate(R.id.action_swapSettingsFragment_to_select_swap_token_graph, AssetSwapFlowFragment.getBundle(payload))
}
override fun openSendCrossChain(destination: AssetPayload, recipientAddress: String?) {
diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml
index 8efb8f4078..d085d337b2 100644
--- a/app/src/main/res/navigation/main_nav_graph.xml
+++ b/app/src/main/res/navigation/main_nav_graph.xml
@@ -217,7 +217,7 @@
+
+
-
-
+
+
-
-
+
+ android:name="io.novafoundation.nova.feature_assets.presentation.receive.flow.asset.AssetReceiveFlowFragment"
+ android:label="AssetReceiveFlowFragment">
+
+
+
+
+ android:name="io.novafoundation.nova.feature_assets.presentation.buy.flow.asset.AssetBuyFlowFragment"
+ android:label="AssetBuyFlowFragment">
+
+
+
+
+ android:id="@+id/sendFlowNetworkFragment"
+ android:name="io.novafoundation.nova.feature_assets.presentation.send.flow.network.NetworkSendFlowFragment"
+ android:label="NetworkSendFlowFragment" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/select_swap_token_nav_graph.xml b/app/src/main/res/navigation/select_swap_token_nav_graph.xml
new file mode 100644
index 0000000000..45123fda55
--- /dev/null
+++ b/app/src/main/res/navigation/select_swap_token_nav_graph.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/start_swap_nav_graph.xml b/app/src/main/res/navigation/start_swap_nav_graph.xml
index 4f0ebf6382..9d9fdb5c4a 100644
--- a/app/src/main/res/navigation/start_swap_nav_graph.xml
+++ b/app/src/main/res/navigation/start_swap_nav_graph.xml
@@ -2,7 +2,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 82a2016493..927170fa5d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
buildscript {
ext {
// App version
- versionName = '8.7.3'
- versionCode = 157
+ versionName = '9.0.0'
+ versionCode = 158
applicationId = "io.novafoundation.nova"
releaseApplicationSuffix = "market"
@@ -176,7 +176,6 @@ buildscript {
jUnitDep = "junit:junit:$junitVersion"
mockitoDep = "org.mockito:mockito-inline:$mockitoVersion"
robolectricDep = "org.robolectric:robolectric:$robolectricVersion"
- robolectricMultidexDep = "org.robolectric:shadows-multidex:$robolectricVersion"
archCoreTestDep = "androidx.arch.core:core-testing:$architectureComponentVersion"
progressButtonDep = "com.github.razir.progressbutton:progressbutton:$progressButtonsVersion"
diff --git a/caip/src/main/java/io/novafoundation/nova/caip/slip44/Slip44CoinRepository.kt b/caip/src/main/java/io/novafoundation/nova/caip/slip44/Slip44CoinRepository.kt
index ab89fb3db7..f53b2a864e 100644
--- a/caip/src/main/java/io/novafoundation/nova/caip/slip44/Slip44CoinRepository.kt
+++ b/caip/src/main/java/io/novafoundation/nova/caip/slip44/Slip44CoinRepository.kt
@@ -2,7 +2,7 @@ package io.novafoundation.nova.caip.slip44
import io.novafoundation.nova.caip.slip44.endpoint.Slip44CoinApi
import io.novafoundation.nova.caip.slip44.endpoint.Slip44CoinRemote
-import io.novafoundation.nova.runtime.ext.unifiedSymbol
+import io.novafoundation.nova.runtime.ext.normalizeSymbol
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -26,7 +26,7 @@ internal class RealSlip44CoinRepository(
.associateBy { it.symbol }
}
- return slip44Coins[chainAsset.unifiedSymbol()]
+ return slip44Coins[chainAsset.normalizeSymbol()]
?.index
?.toIntOrNull()
}
diff --git a/common/build.gradle b/common/build.gradle
index 0a119a8950..fa5059f0cb 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -42,6 +42,9 @@ android {
buildConfigField "String", "LEDGER_BLEUTOOTH_GUIDE", "\"https://support.ledger.com/hc/en-us/articles/360019138694-Set-up-Bluetooth-connection\""
buildConfigField "String", "APP_UPDATE_SOURCE_LINK", "\"https://play.google.com/store/apps/details?id=io.novafoundation.nova.market\""
+
+ buildConfigField "String", "ASSET_COLORED_ICON_URL", "\"https://raw.githubusercontent.com/novasamatech/nova-utils/master/icons/tokens/colored\""
+ buildConfigField "String", "ASSET_WHITE_ICON_URL", "\"https://raw.githubusercontent.com/novasamatech/nova-utils/master/icons/tokens/white/v1\""
}
buildTypes {
diff --git a/common/src/main/java/io/novafoundation/nova/common/data/model/AssetIconMode.kt b/common/src/main/java/io/novafoundation/nova/common/data/model/AssetIconMode.kt
new file mode 100644
index 0000000000..76fda50d5f
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/data/model/AssetIconMode.kt
@@ -0,0 +1,5 @@
+package io.novafoundation.nova.common.data.model
+
+enum class AssetIconMode {
+ COLORED, WHITE
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/data/model/AssetViewMode.kt b/common/src/main/java/io/novafoundation/nova/common/data/model/AssetViewMode.kt
new file mode 100644
index 0000000000..04a0870730
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/data/model/AssetViewMode.kt
@@ -0,0 +1,12 @@
+package io.novafoundation.nova.common.data.model
+
+enum class AssetViewMode {
+ TOKENS, NETWORKS
+}
+
+fun AssetViewMode.switch(): AssetViewMode {
+ return when (this) {
+ AssetViewMode.TOKENS -> AssetViewMode.NETWORKS
+ AssetViewMode.NETWORKS -> AssetViewMode.TOKENS
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsIconModeRepository.kt b/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsIconModeRepository.kt
new file mode 100644
index 0000000000..1ff99d29f8
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsIconModeRepository.kt
@@ -0,0 +1,52 @@
+package io.novafoundation.nova.common.data.repository
+
+import io.novafoundation.nova.common.data.model.AssetIconMode
+import io.novafoundation.nova.common.data.storage.Preferences
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+interface AssetsIconModeRepository {
+ fun assetsIconModeFlow(): Flow
+
+ fun setAssetsIconMode(assetsViewMode: AssetIconMode)
+
+ fun getIconMode(): AssetIconMode
+}
+
+private const val PREFS_ASSETS_ICON_MODE = "PREFS_ASSETS_ICON_MODE"
+private val ASSET_ICON_MODE_DEFAULT = AssetIconMode.COLORED
+
+class RealAssetsIconModeRepository(
+ private val preferences: Preferences
+) : AssetsIconModeRepository {
+
+ override fun assetsIconModeFlow(): Flow {
+ return preferences.stringFlow(PREFS_ASSETS_ICON_MODE)
+ .map {
+ it?.fromPrefsValue() ?: ASSET_ICON_MODE_DEFAULT
+ }
+ }
+
+ override fun setAssetsIconMode(assetsViewMode: AssetIconMode) {
+ preferences.putString(PREFS_ASSETS_ICON_MODE, assetsViewMode.toPrefsValue())
+ }
+
+ override fun getIconMode(): AssetIconMode {
+ return preferences.getString(PREFS_ASSETS_ICON_MODE)?.fromPrefsValue() ?: ASSET_ICON_MODE_DEFAULT
+ }
+
+ private fun AssetIconMode.toPrefsValue(): String {
+ return when (this) {
+ AssetIconMode.COLORED -> "colored"
+ AssetIconMode.WHITE -> "white"
+ }
+ }
+
+ private fun String.fromPrefsValue(): AssetIconMode? {
+ return when (this) {
+ "colored" -> AssetIconMode.COLORED
+ "white" -> AssetIconMode.WHITE
+ else -> null
+ }
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsViewModeRepository.kt b/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsViewModeRepository.kt
new file mode 100644
index 0000000000..072c9cbcf1
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/data/repository/AssetsViewModeRepository.kt
@@ -0,0 +1,55 @@
+package io.novafoundation.nova.common.data.repository
+
+import io.novafoundation.nova.common.data.model.AssetViewMode
+import io.novafoundation.nova.common.data.storage.Preferences
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+interface AssetsViewModeRepository {
+
+ fun getAssetViewMode(): AssetViewMode
+
+ fun assetsViewModeFlow(): Flow
+
+ suspend fun setAssetsViewMode(assetsViewMode: AssetViewMode)
+}
+
+private const val PREFS_ASSETS_VIEW_MODE = "PREFS_ASSETS_VIEW_MODE"
+private val ASSET_VIEW_MODE_DEFAULT = AssetViewMode.TOKENS
+
+class RealAssetsViewModeRepository(
+ private val preferences: Preferences
+) : AssetsViewModeRepository {
+
+ override fun getAssetViewMode(): AssetViewMode {
+ return preferences.getString(PREFS_ASSETS_VIEW_MODE)?.fromPrefsValue() ?: ASSET_VIEW_MODE_DEFAULT
+ }
+
+ override fun assetsViewModeFlow(): Flow {
+ return preferences.stringFlow(PREFS_ASSETS_VIEW_MODE)
+ .map {
+ it?.fromPrefsValue() ?: ASSET_VIEW_MODE_DEFAULT
+ }
+ }
+
+ override suspend fun setAssetsViewMode(assetsViewMode: AssetViewMode) = withContext(Dispatchers.IO) {
+ preferences.putString(PREFS_ASSETS_VIEW_MODE, assetsViewMode.toPrefsValue())
+ }
+
+ private fun AssetViewMode.toPrefsValue(): String {
+ return when (this) {
+ AssetViewMode.NETWORKS -> "networks"
+ AssetViewMode.TOKENS -> "tokens"
+ }
+ }
+
+ private fun String.fromPrefsValue(): AssetViewMode? {
+ return when (this) {
+ "networks" -> AssetViewMode.NETWORKS
+ "tokens" -> AssetViewMode.TOKENS
+ else -> null
+ }
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt b/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt
index 9c52fe3e0e..01b93537de 100644
--- a/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt
@@ -14,12 +14,15 @@ import io.novafoundation.nova.common.data.network.HttpExceptionHandler
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.data.network.coingecko.CoinGeckoLinkParser
import io.novafoundation.nova.common.data.network.rpc.SocketSingleRequestExecutor
+import io.novafoundation.nova.common.data.repository.AssetsIconModeRepository
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences
import io.novafoundation.nova.common.di.modules.Caching
+import io.novafoundation.nova.common.domain.interactor.AssetViewModeInteractor
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
import io.novafoundation.nova.common.interfaces.FileCache
@@ -29,6 +32,7 @@ import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
import io.novafoundation.nova.common.mixin.api.NetworkStateMixin
import io.novafoundation.nova.common.mixin.condition.ConditionMixinFactory
import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory
+import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.AppVersionProvider
import io.novafoundation.nova.common.resources.ClipboardManager
import io.novafoundation.nova.common.resources.ContextManager
@@ -63,6 +67,36 @@ import java.util.Random
interface CommonApi {
+ val systemCallExecutor: SystemCallExecutor
+
+ val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
+
+ val resourcesHintsMixinFactory: ResourcesHintsMixinFactory
+
+ val okHttpClient: OkHttpClient
+
+ val fileCache: FileCache
+
+ val permissionsAskerFactory: PermissionsAskerFactory
+
+ val bluetoothManager: BluetoothManager
+
+ val locationManager: LocationManager
+
+ val listChooserMixinFactory: ListChooserMixin.Factory
+
+ val partialRetriableMixinFactory: PartialRetriableMixin.Factory
+
+ val automaticInteractionGate: AutomaticInteractionGate
+
+ val bannerVisibilityRepository: BannerVisibilityRepository
+
+ val provideActivityIntentProvider: ActivityIntentProvider
+
+ val googleApiAvailabilityProvider: GoogleApiAvailabilityProvider
+
+ val coinGeckoLinkParser: CoinGeckoLinkParser
+
fun computationalCache(): ComputationalCache
fun imageLoader(): ImageLoader
@@ -154,33 +188,11 @@ interface CommonApi {
fun buildTypeProvider(): BuildTypeProvider
- val systemCallExecutor: SystemCallExecutor
-
- val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
-
- val resourcesHintsMixinFactory: ResourcesHintsMixinFactory
-
- val okHttpClient: OkHttpClient
-
- val fileCache: FileCache
-
- val permissionsAskerFactory: PermissionsAskerFactory
-
- val bluetoothManager: BluetoothManager
-
- val locationManager: LocationManager
-
- val listChooserMixinFactory: ListChooserMixin.Factory
-
- val partialRetriableMixinFactory: PartialRetriableMixin.Factory
+ fun assetsViewModeRepository(): AssetsViewModeRepository
- val automaticInteractionGate: AutomaticInteractionGate
-
- val bannerVisibilityRepository: BannerVisibilityRepository
+ fun assetsIconModeService(): AssetsIconModeRepository
- val provideActivityIntentProvider: ActivityIntentProvider
+ fun assetIconProvider(): AssetIconProvider
- val googleApiAvailabilityProvider: GoogleApiAvailabilityProvider
-
- val coinGeckoLinkParser: CoinGeckoLinkParser
+ fun assetViewModeInteractor(): AssetViewModeInteractor
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt b/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt
index 8de4f7e85f..bf958cbee2 100644
--- a/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt
@@ -9,6 +9,7 @@ import coil.ImageLoader
import coil.decode.SvgDecoder
import dagger.Module
import dagger.Provides
+import io.novafoundation.nova.common.BuildConfig
import io.novafoundation.nova.common.address.AddressIconGenerator
import io.novafoundation.nova.common.address.CachingAddressIconGenerator
import io.novafoundation.nova.common.address.StatelessAddressIconGenerator
@@ -19,7 +20,11 @@ import io.novafoundation.nova.common.data.RealGoogleApiAvailabilityProvider
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.data.memory.RealComputationalCache
import io.novafoundation.nova.common.data.network.coingecko.CoinGeckoLinkParser
+import io.novafoundation.nova.common.data.repository.AssetsIconModeRepository
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
+import io.novafoundation.nova.common.data.repository.RealAssetsIconModeRepository
+import io.novafoundation.nova.common.data.repository.RealAssetsViewModeRepository
import io.novafoundation.nova.common.data.repository.RealBannerVisibilityRepository
import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1
import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1Impl
@@ -30,6 +35,8 @@ import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences
import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferencesImpl
import io.novafoundation.nova.common.data.storage.encrypt.EncryptionUtil
import io.novafoundation.nova.common.di.scope.ApplicationScope
+import io.novafoundation.nova.common.domain.interactor.AssetViewModeInteractor
+import io.novafoundation.nova.common.domain.interactor.RealAssetViewModeInteractor
import io.novafoundation.nova.common.interfaces.FileCache
import io.novafoundation.nova.common.interfaces.FileProvider
import io.novafoundation.nova.common.interfaces.InternalFileSystemCache
@@ -40,6 +47,8 @@ import io.novafoundation.nova.common.mixin.condition.ConditionMixinFactory
import io.novafoundation.nova.common.mixin.condition.RealConditionMixinFactory
import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory
import io.novafoundation.nova.common.mixin.impl.CustomDialogProvider
+import io.novafoundation.nova.common.presentation.AssetIconProvider
+import io.novafoundation.nova.common.presentation.RealAssetIconProvider
import io.novafoundation.nova.common.resources.AppVersionProvider
import io.novafoundation.nova.common.resources.ClipboardManager
import io.novafoundation.nova.common.resources.ContextManager
@@ -347,4 +356,28 @@ class CommonModule {
fun provideCoinGeckoLinkParser(): CoinGeckoLinkParser {
return CoinGeckoLinkParser()
}
+
+ @Provides
+ @ApplicationScope
+ fun provideAssetsViewModeRepository(preferences: Preferences): AssetsViewModeRepository = RealAssetsViewModeRepository(preferences)
+
+ @Provides
+ @ApplicationScope
+ fun provideAssetViewModeInteractor(repository: AssetsViewModeRepository): AssetViewModeInteractor {
+ return RealAssetViewModeInteractor(repository)
+ }
+
+ @Provides
+ @ApplicationScope
+ fun provideAssetsIconModeRepository(preferences: Preferences): AssetsIconModeRepository = RealAssetsIconModeRepository(preferences)
+
+ @Provides
+ @ApplicationScope
+ fun provideAssetIconProvider(repository: AssetsIconModeRepository): AssetIconProvider {
+ return RealAssetIconProvider(
+ repository,
+ BuildConfig.ASSET_COLORED_ICON_URL,
+ BuildConfig.ASSET_WHITE_ICON_URL,
+ )
+ }
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/domain/interactor/AssetViewModeInteractor.kt b/common/src/main/java/io/novafoundation/nova/common/domain/interactor/AssetViewModeInteractor.kt
new file mode 100644
index 0000000000..f9a7d15fe3
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/domain/interactor/AssetViewModeInteractor.kt
@@ -0,0 +1,30 @@
+package io.novafoundation.nova.common.domain.interactor
+
+import io.novafoundation.nova.common.data.model.AssetViewMode
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
+import kotlinx.coroutines.flow.Flow
+
+interface AssetViewModeInteractor {
+
+ fun getAssetViewMode(): AssetViewMode
+
+ fun assetsViewModeFlow(): Flow
+
+ suspend fun setAssetsViewMode(assetsViewMode: AssetViewMode)
+}
+
+class RealAssetViewModeInteractor(
+ private val assetsViewModeRepository: AssetsViewModeRepository
+) : AssetViewModeInteractor {
+ override fun getAssetViewMode(): AssetViewMode {
+ return assetsViewModeRepository.getAssetViewMode()
+ }
+
+ override fun assetsViewModeFlow(): Flow {
+ return assetsViewModeRepository.assetsViewModeFlow()
+ }
+
+ override suspend fun setAssetsViewMode(assetsViewMode: AssetViewMode) {
+ assetsViewModeRepository.setAssetsViewMode(assetsViewMode)
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt b/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt
new file mode 100644
index 0000000000..b764abf57e
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt
@@ -0,0 +1,54 @@
+package io.novafoundation.nova.common.presentation
+
+import io.novafoundation.nova.common.R
+import io.novafoundation.nova.common.data.model.AssetIconMode
+import io.novafoundation.nova.common.data.repository.AssetsIconModeRepository
+import io.novafoundation.nova.common.utils.images.Icon
+import io.novafoundation.nova.common.utils.images.asIcon
+
+interface AssetIconProvider {
+
+ companion object;
+
+ fun getAssetIconOrFallback(iconName: String): Icon
+
+ fun getAssetIconOrFallback(iconName: String, iconMode: AssetIconMode): Icon
+}
+
+class RealAssetIconProvider(
+ private val assetsIconModeRepository: AssetsIconModeRepository,
+ private val coloredBaseUrl: String,
+ private val whiteBaseUrl: String
+) : AssetIconProvider {
+
+ override fun getAssetIconOrFallback(iconName: String): Icon {
+ return getAssetIconOrFallback(iconName, assetsIconModeRepository.getIconMode())
+ }
+
+ override fun getAssetIconOrFallback(iconName: String, iconMode: AssetIconMode): Icon {
+ val iconUrl = when (iconMode) {
+ AssetIconMode.COLORED -> "$coloredBaseUrl/$iconName"
+ AssetIconMode.WHITE -> "$whiteBaseUrl/$iconName"
+ }
+
+ return iconUrl.asIcon()
+ }
+}
+
+val AssetIconProvider.Companion.fallbackIcon: Icon
+ get() = R.drawable.ic_nova.asIcon()
+
+fun AssetIconProvider.getAssetIconOrFallback(
+ iconName: String?,
+ fallback: Icon = AssetIconProvider.fallbackIcon
+): Icon {
+ return iconName?.let { getAssetIconOrFallback(it) } ?: fallback
+}
+
+fun AssetIconProvider.getAssetIconOrFallback(
+ iconName: String?,
+ iconMode: AssetIconMode,
+ fallback: Icon = AssetIconProvider.fallbackIcon
+): Icon {
+ return iconName?.let { getAssetIconOrFallback(it, iconMode) } ?: fallback
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/presentation/ColoredText.kt b/common/src/main/java/io/novafoundation/nova/common/presentation/ColoredText.kt
index 2fe200f4a3..89dc088417 100644
--- a/common/src/main/java/io/novafoundation/nova/common/presentation/ColoredText.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/presentation/ColoredText.kt
@@ -6,7 +6,7 @@ import io.novafoundation.nova.common.utils.letOrHide
import io.novafoundation.nova.common.utils.setTextColorRes
data class ColoredText(
- val text: String,
+ val text: CharSequence,
@ColorRes val colorRes: Int,
)
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/ContextExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/ContextExt.kt
index c203835eb0..89068ac9be 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/ContextExt.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/ContextExt.kt
@@ -16,6 +16,7 @@ import androidx.annotation.StyleRes
import androidx.core.content.ContextCompat
import io.novafoundation.nova.common.R
import io.novafoundation.nova.common.view.shape.addRipple
+import io.novafoundation.nova.common.view.shape.getMaskedRipple
import io.novafoundation.nova.common.view.shape.getRippleMask
import io.novafoundation.nova.common.view.shape.getRoundedCornerDrawable
import kotlin.math.roundToInt
@@ -108,6 +109,8 @@ interface WithContextExtensions {
val Float.dpF: Float
get() = dpF(providedContext)
+ fun getRippleDrawable(cornerSizeInDp: Int) = providedContext.getMaskedRipple(cornerSizeInDp)
+
fun addRipple(to: Drawable, mask: Drawable? = getRippleMask()) = providedContext.addRipple(to, mask)
fun Drawable.withRippleMask(mask: Drawable = getRippleMask()) = addRipple(this, mask)
@@ -145,3 +148,7 @@ fun Drawable.withRippleMask(mask: Drawable = getRippleMask()) = context.addRippl
context(View)
val Int.dp: Int
get() = dp(this@View.context)
+
+context(View)
+val Int.dpF: Float
+ get() = dpF(this@View.context)
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FlowExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FlowExt.kt
index 21560bebc6..06686e8ced 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/FlowExt.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/FlowExt.kt
@@ -56,6 +56,10 @@ inline fun Flow>.filterList(crossinline handler: suspend (T) -> Bool
list.filter { item -> handler(item) }
}
+inline fun Flow>.filterSet(crossinline handler: suspend (T) -> Boolean) = map { set ->
+ set.filter { item -> handler(item) }.toSet()
+}
+
inline fun Flow>.mapList(crossinline mapper: suspend (T) -> R) = map { list ->
list.map { item -> mapper(item) }
}
@@ -141,6 +145,8 @@ inline fun withFlowScope(crossinline block: suspend (scope: CoroutineScope)
fun combineToPair(flow1: Flow, flow2: Flow): Flow> = combine(flow1, flow2, ::Pair)
+fun combineToTriple(flow1: Flow, flow2: Flow, flow3: Flow): Flow> = combine(flow1, flow2, flow3, ::Triple)
+
/**
* Modifies flow so that it firstly emits [LoadingState.Loading] state for each element from upstream.
* Then, it constructs new source via [sourceSupplier] and emits all of its items wrapped into [LoadingState.Loaded] state
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt
index 4717260d45..c6e87dfe0e 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt
@@ -23,6 +23,7 @@ import java.util.Collections
import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.launch
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@@ -597,3 +598,7 @@ fun Calendar.resetDay() {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
+
+inline fun CoroutineScope.launchUnit(crossinline block: suspend CoroutineScope.() -> Unit) {
+ launch { block() }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/PayloadCreator.kt b/common/src/main/java/io/novafoundation/nova/common/utils/PayloadCreator.kt
new file mode 100644
index 0000000000..7b941fd5e3
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/PayloadCreator.kt
@@ -0,0 +1,25 @@
+package io.novafoundation.nova.common.utils
+
+import android.os.Bundle
+import android.os.Parcelable
+import io.novafoundation.nova.common.base.BaseFragment
+
+const val KEY_PAYLOAD = "KEY_PAYLOAD"
+
+interface PayloadCreator {
+
+ fun createPayload(payload: T): Bundle
+}
+
+class FragmentPayloadCreator : PayloadCreator {
+
+ override fun createPayload(payload: T): Bundle {
+ return Bundle().apply {
+ putParcelable(KEY_PAYLOAD, payload)
+ }
+ }
+}
+
+fun BaseFragment<*>.payload(): T {
+ return requireArguments().getParcelable(KEY_PAYLOAD)!!
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/QrCodeGenerator.kt b/common/src/main/java/io/novafoundation/nova/common/utils/QrCodeGenerator.kt
index 69c08a9d6b..31fbfd5e08 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/QrCodeGenerator.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/QrCodeGenerator.kt
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.google.zxing.qrcode.encoder.Encoder
+import com.google.zxing.qrcode.encoder.QRCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -22,10 +23,14 @@ class QrCodeGenerator(
const val MAX_PAYLOAD_LENGTH = 512
}
+ fun generateQrCode(input: String): QRCode {
+ val hints = HashMap()
+ return Encoder.encode(input, ErrorCorrectionLevel.H, hints)
+ }
+
suspend fun generateQrBitmap(input: String): Bitmap {
return withContext(Dispatchers.Default) {
- val hints = HashMap()
- val qrCode = Encoder.encode(input, ErrorCorrectionLevel.H, hints)
+ val qrCode = generateQrCode(input)
val byteMatrix = qrCode.matrix
val width = byteMatrix.width + PADDING_SIZE
val height = byteMatrix.height + PADDING_SIZE
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableDSL.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableDSL.kt
index e358d3a78c..2636f883f5 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableDSL.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableDSL.kt
@@ -48,7 +48,7 @@ class SpannableBuilder(private val resourceManager: ResourceManager) {
private val builder = SpannableStringBuilder()
- fun appendColored(text: String, @ColorRes color: Int) {
+ fun appendColored(text: CharSequence, @ColorRes color: Int) {
val span = ForegroundColorSpan(resourceManager.getColor(color))
append(text, span)
@@ -60,7 +60,7 @@ class SpannableBuilder(private val resourceManager: ResourceManager) {
return appendColored(text, color)
}
- fun append(text: String) {
+ fun append(text: CharSequence) {
builder.append(text)
}
@@ -75,7 +75,7 @@ class SpannableBuilder(private val resourceManager: ResourceManager) {
fun build(): SpannableString = SpannableString(builder)
- private fun append(text: String, span: Any): SpannableBuilder {
+ private fun append(text: CharSequence, span: Any): SpannableBuilder {
builder.append(text, span, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
return this
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/TokenSymbol.kt b/common/src/main/java/io/novafoundation/nova/common/utils/TokenSymbol.kt
index 24af37c2c6..624e0194bb 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/TokenSymbol.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/TokenSymbol.kt
@@ -7,11 +7,17 @@ import java.math.RoundingMode
@JvmInline
value class TokenSymbol(val value: String) {
+ companion object; // extensions
+
override fun toString() = value
}
fun String.asTokenSymbol() = TokenSymbol(this)
+fun BigDecimal.formatTokenAmount(roundingMode: RoundingMode = RoundingMode.FLOOR): String {
+ return format(roundingMode)
+}
+
fun BigDecimal.formatTokenAmount(tokenSymbol: TokenSymbol, roundingMode: RoundingMode = RoundingMode.FLOOR): String {
return format(roundingMode).withTokenSymbol(tokenSymbol)
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/ViewExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/ViewExt.kt
index 64ca9c9624..76402edeac 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/ViewExt.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/ViewExt.kt
@@ -264,6 +264,10 @@ fun RecyclerView.findFirstVisiblePosition(): Int {
return (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
}
+fun RecyclerView.findLastVisiblePosition(): Int {
+ return (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
+}
+
fun ScrollView.scrollOnFocusTo(vararg focusableTargets: View) {
val listener = View.OnFocusChangeListener { view, hasFocus ->
if (hasFocus) {
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/DynamicPrecisionFormatter.kt b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/DynamicPrecisionFormatter.kt
index b5f2554719..5cfb1a8c47 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/DynamicPrecisionFormatter.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/DynamicPrecisionFormatter.kt
@@ -3,6 +3,7 @@ package io.novafoundation.nova.common.utils.formatting
import java.lang.Integer.max
import java.math.BigDecimal
import java.math.RoundingMode
+import java.text.DecimalFormat
import kotlin.math.min
class DynamicPrecisionFormatter(
@@ -10,6 +11,8 @@ class DynamicPrecisionFormatter(
private val minPrecision: Int,
) : NumberFormatter {
+ private val patternCache = mutableMapOf()
+
override fun format(number: BigDecimal, roundingMode: RoundingMode): String {
// scale() - total amount of digits after 0.,
// precision() - amount of non-zero digits in decimal part
@@ -18,6 +21,11 @@ class DynamicPrecisionFormatter(
val formattingPrecision = max(minScale, requiredPrecision)
- return decimalFormatterFor(patternWith(formattingPrecision), roundingMode).format(number)
+ val formatter = patternCache.getOrPut(formattingPrecision) { decimalFormatterFor(patternWith(formattingPrecision)) }
+ if (formatter.roundingMode != roundingMode) {
+ formatter.roundingMode = roundingMode
+ }
+
+ return formatter.format(number)
}
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/FixedPrecisionFormatter.kt b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/FixedPrecisionFormatter.kt
index 361e7e2afc..c03638b908 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/FixedPrecisionFormatter.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/FixedPrecisionFormatter.kt
@@ -5,8 +5,12 @@ import java.math.RoundingMode
class FixedPrecisionFormatter(private val precision: Int) : NumberFormatter {
+ private val delegate = decimalFormatterFor(patternWith(precision))
+
override fun format(number: BigDecimal, roundingMode: RoundingMode): String {
- val delegate = decimalFormatterFor(patternWith(precision), roundingMode)
+ if (delegate.roundingMode != roundingMode) {
+ delegate.roundingMode = roundingMode
+ }
return delegate.format(number)
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt
index 8c8b907eef..1c58aa033f 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt
@@ -169,7 +169,7 @@ fun formatDateISO_8601_NoMs(date: Date): String {
return dateTimeFormatISO_8601_NoMs.format(date)
}
-fun decimalFormatterFor(pattern: String, roundingMode: RoundingMode): DecimalFormat {
+fun decimalFormatterFor(pattern: String): DecimalFormat {
return DecimalFormat(pattern).apply {
val symbols = decimalFormatSymbols
@@ -178,12 +178,11 @@ fun decimalFormatterFor(pattern: String, roundingMode: RoundingMode): DecimalFor
decimalFormatSymbols = symbols
- this.roundingMode = roundingMode
decimalFormatSymbols = decimalFormatSymbols
}
}
-fun String.toAmountWithFraction(): AmountWithFraction {
+fun CharSequence.toAmountWithFraction(): AmountWithFraction {
val amountAndFraction = this.split(DECIMAL_SEPARATOR)
val amount = amountAndFraction[0]
val fraction = amountAndFraction.getOrNull(1)
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt b/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt
index 54c0b3e420..1f78a20199 100644
--- a/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt
@@ -11,11 +11,11 @@ import io.novafoundation.nova.common.utils.makeVisible
sealed class Icon {
- class FromLink(val data: String) : Icon()
+ data class FromLink(val data: String) : Icon()
- class FromDrawable(val data: Drawable) : Icon()
+ data class FromDrawable(val data: Drawable) : Icon()
- class FromDrawableRes(@DrawableRes val res: Int) : Icon()
+ data class FromDrawableRes(@DrawableRes val res: Int) : Icon()
}
typealias ExtraImageRequestBuilding = ImageRequest.Builder.() -> Unit
@@ -40,3 +40,9 @@ fun ImageView.setIconOrMakeGone(icon: Icon?, imageLoader: ImageLoader, builder:
fun Drawable.asIcon() = Icon.FromDrawable(this)
fun @receiver:DrawableRes Int.asIcon() = Icon.FromDrawableRes(this)
fun String.asIcon() = Icon.FromLink(this)
+
+fun ImageLoader.Companion.formatIcon(icon: Icon): Any = when (icon) {
+ is Icon.FromDrawable -> icon.data
+ is Icon.FromDrawableRes -> icon.res
+ is Icon.FromLink -> icon.data
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAdapter.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAdapter.kt
new file mode 100644
index 0000000000..2ea0f99561
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAdapter.kt
@@ -0,0 +1,8 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableBaseItem
+
+interface ExpandableAdapter {
+
+ fun getItems(): List
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationExt.kt
new file mode 100644
index 0000000000..a54d6a34cc
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationExt.kt
@@ -0,0 +1,18 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import io.novafoundation.nova.common.utils.recyclerView.expandable.animator.ExpandableAnimationItemState
+
+fun ExpandableAnimationItemState.flippedFraction(): Float {
+ return 1f - animationFraction
+}
+
+fun Float.flippedFraction(): Float {
+ return 1f - this
+}
+
+fun ExpandableAnimationItemState.expandingFraction(): Float {
+ return when (animationType) {
+ ExpandableAnimationItemState.Type.EXPANDING -> animationFraction
+ ExpandableAnimationItemState.Type.COLLAPSING -> flippedFraction()
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationSettings.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationSettings.kt
new file mode 100644
index 0000000000..2d001e0117
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableAnimationSettings.kt
@@ -0,0 +1,7 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import android.view.animation.Interpolator
+
+class ExpandableAnimationSettings(val duration: Long, val interpolator: Interpolator) {
+ companion object;
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemAnimator.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemAnimator.kt
new file mode 100644
index 0000000000..2c7a806f42
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemAnimator.kt
@@ -0,0 +1,299 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.view.ViewPropertyAnimator
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import androidx.recyclerview.widget.SimpleItemAnimator
+import io.novafoundation.nova.common.utils.recyclerView.expandable.animator.ExpandableAnimationItemState
+import io.novafoundation.nova.common.utils.recyclerView.expandable.animator.ExpandableAnimator
+
+/**
+ * Potential problems:
+ * - If in one time we will run add and move animation or remove and move animation - one of an animation will be cancelled
+ */
+abstract class ExpandableItemAnimator(
+ private val settings: ExpandableAnimationSettings,
+ private val expandableAnimator: ExpandableAnimator
+) : SimpleItemAnimator() {
+
+ private var preparedForAnimation = false
+
+ private val addAnimations = mutableMapOf>() // Parent item to children
+ private val removeAnimations = mutableMapOf>() // Parent item to children
+ private val moveAnimations = mutableListOf()
+
+ private val pendingAddAnimations = mutableSetOf()
+ private val pendingRemoveAnimations = mutableSetOf()
+ private val pendingMoveAnimations = mutableSetOf()
+
+ init {
+ addDuration = settings.duration
+ removeDuration = settings.duration
+ moveDuration = settings.duration
+
+ supportsChangeAnimations = false
+ }
+
+ /**
+ * Use this method before adapter.submitList() to prepare items for animation.
+ * Item animations will be skipped otherwise
+ */
+ fun prepareForAnimation() {
+ preparedForAnimation = true
+ }
+
+ override fun animateAdd(holder: ViewHolder): Boolean {
+ val notPreparedForAnimation = !preparedForAnimation
+ val notExpandableChildItem = holder !is ExpandableChildViewHolder || holder.expandableItem == null
+ if (notPreparedForAnimation || notExpandableChildItem) {
+ dispatchAddFinished(holder)
+ return false
+ }
+
+ val item = (holder as ExpandableChildViewHolder).expandableItem!!
+
+ // Reset move state helps clear translationY when animation is being to be canceled
+ if (pendingMoveAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+
+ resetMoveState(holder)
+ }
+
+ if (pendingRemoveAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+ } else {
+ preAddImpl(holder)
+ }
+
+ if (item.groupId !in addAnimations) addAnimations[item.groupId] = mutableListOf()
+ addAnimations[item.groupId]?.add(holder)
+
+ expandableAnimator.prepareAnimationToState(item.groupId, ExpandableAnimationItemState.Type.EXPANDING)
+
+ return true
+ }
+
+ override fun animateRemove(holder: ViewHolder): Boolean {
+ val notPreparedForAnimation = !preparedForAnimation
+ val notExpandableChildItem = holder !is ExpandableChildViewHolder || holder.expandableItem == null
+ if (notPreparedForAnimation || notExpandableChildItem) {
+ dispatchRemoveFinished(holder)
+ return false
+ }
+
+ val item = (holder as ExpandableChildViewHolder).expandableItem!!
+
+ // Reset move state helps clear translationY when animation is being to be canceled
+ if (pendingMoveAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+
+ resetMoveState(holder)
+ }
+
+ if (pendingAddAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+ } else {
+ preRemoveImpl(holder)
+ }
+
+ if (item.groupId !in removeAnimations) removeAnimations[item.groupId] = mutableListOf()
+ removeAnimations[item.groupId]?.add(holder)
+
+ expandableAnimator.prepareAnimationToState(item.groupId, ExpandableAnimationItemState.Type.COLLAPSING)
+
+ return true
+ }
+
+ override fun animateMove(holder: ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean {
+ val notPreparedForAnimation = !preparedForAnimation
+ if (notPreparedForAnimation || holder !is ExpandableBaseViewHolder<*>) {
+ dispatchMoveFinished(holder)
+ return false
+ }
+
+ // Reset add state helps clear alpha and scale when animation is being to be canceled
+ if (pendingAddAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+ resetAddState(holder)
+ }
+
+ // Reset remove state helps clear alpha and scale when animation is being to be canceled
+ if (pendingRemoveAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+ }
+
+ if (pendingMoveAnimations.contains(holder)) {
+ holder.itemView.animate().cancel()
+ }
+
+ preMoveImpl(holder, fromY, toY)
+ moveAnimations.add(holder)
+ return true
+ }
+
+ override fun animateChange(oldHolder: ViewHolder?, newHolder: ViewHolder?, fromLeft: Int, fromTop: Int, toLeft: Int, toTop: Int): Boolean {
+ if (oldHolder == newHolder) {
+ dispatchChangeFinished(newHolder, false)
+ } else {
+ dispatchChangeFinished(oldHolder, true)
+ dispatchChangeFinished(newHolder, false)
+ }
+ return false
+ }
+
+ override fun runPendingAnimations() {
+ // Add animation + expand items
+ runExpandableAnimationFor(addAnimations, pendingAddAnimations) { animateAddImpl(it) }
+
+ // Remove animation + collapse items
+ runExpandableAnimationFor(removeAnimations, pendingRemoveAnimations) { animateRemoveImpl(it) }
+
+ // Move animation
+ val animatingViewHolders = moveAnimations.toList()
+ moveAnimations.clear()
+
+ for (holder in animatingViewHolders) {
+ animateMoveImpl(holder)
+ }
+
+ pendingMoveAnimations.addAll(animatingViewHolders)
+
+ // Set prepare for animation = false to return to skipping animations
+ if (pendingAddAnimations.isNotEmpty() || pendingRemoveAnimations.isNotEmpty() || pendingMoveAnimations.isNotEmpty()) {
+ preparedForAnimation = false
+ }
+ }
+
+ private fun runExpandableAnimationFor(
+ animationGroup: MutableMap>,
+ pendingAnimations: MutableSet,
+ runAnimation: (ViewHolder) -> Unit
+ ) {
+ val parentItemIds = animationGroup.keys.toList()
+ val animatingViewHolders = animationGroup.flatMap { (_, viewHolders) -> viewHolders }
+ animationGroup.clear()
+
+ parentItemIds.forEach { expandableAnimator.runAnimationFor(it) }
+ for (holder in animatingViewHolders) {
+ runAnimation(holder)
+ }
+
+ pendingAnimations.addAll(animatingViewHolders)
+ }
+
+ abstract fun preAddImpl(holder: ViewHolder)
+
+ abstract fun getAddAnimator(holder: ViewHolder): ViewPropertyAnimator
+
+ abstract fun preRemoveImpl(holder: ViewHolder)
+
+ abstract fun getRemoveAnimator(holder: ViewHolder): ViewPropertyAnimator
+
+ abstract fun preMoveImpl(holder: ViewHolder, fromY: Int, toY: Int)
+
+ abstract fun getMoveAnimator(holder: ViewHolder): ViewPropertyAnimator
+
+ abstract fun resetAddState(holder: ViewHolder)
+
+ abstract fun resetRemoveState(holder: ViewHolder)
+
+ abstract fun resetMoveState(holder: ViewHolder)
+
+ private fun animateAddImpl(holder: ViewHolder) {
+ val animation = getAddAnimator(holder)
+ animation.setInterpolator(settings.interpolator)
+ .setDuration(settings.duration)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ addFinished(holder)
+ animation.setListener(null)
+ }
+ }).start()
+ }
+
+ private fun animateRemoveImpl(holder: ViewHolder) {
+ val animation = getRemoveAnimator(holder)
+ animation.setInterpolator(settings.interpolator)
+ .setDuration(settings.duration)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ resetRemoveState(holder)
+ removeFinished(holder)
+ animation.setListener(null)
+ }
+ }).start()
+ }
+
+ private fun animateMoveImpl(holder: ViewHolder) {
+ val animation = getMoveAnimator(holder)
+ animation.setInterpolator(settings.interpolator)
+ .setDuration(settings.duration)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ moveFinished(holder)
+ animation.setListener(null)
+ }
+ }).start()
+ }
+
+ override fun endAnimation(viewHolder: ViewHolder) {
+ viewHolder.itemView.animate().cancel()
+ }
+
+ override fun endAnimations() {
+ pendingAddAnimations.iterator().forEach { it.itemView.animate().cancel() }
+ pendingAddAnimations.clear()
+
+ pendingRemoveAnimations.iterator().forEach { it.itemView.animate().cancel() }
+ pendingRemoveAnimations.clear()
+
+ pendingMoveAnimations.iterator().forEach { it.itemView.animate().cancel() }
+ pendingMoveAnimations.clear()
+
+ addAnimations.clear()
+ removeAnimations.clear()
+ moveAnimations.clear()
+
+ expandableAnimator.cancelAnimations()
+ }
+
+ override fun isRunning(): Boolean {
+ return addAnimations.isNotEmpty() ||
+ removeAnimations.isNotEmpty() ||
+ moveAnimations.isNotEmpty() ||
+ pendingAddAnimations.isNotEmpty() ||
+ pendingRemoveAnimations.isNotEmpty() ||
+ pendingMoveAnimations.isNotEmpty()
+ }
+
+ private fun addFinished(holder: ViewHolder) {
+ pendingAddAnimations.remove(holder)
+ internalDispatchAnimationFinished(holder)
+ }
+
+ private fun removeFinished(holder: ViewHolder) {
+ pendingRemoveAnimations.remove(holder)
+ internalDispatchAnimationFinished(holder)
+ }
+
+ private fun moveFinished(holder: ViewHolder) {
+ pendingMoveAnimations.remove(holder)
+ internalDispatchAnimationFinished(holder)
+ }
+
+ private fun internalDispatchAnimationFinished(holder: ViewHolder) {
+ if (holder in pendingAddAnimations) return
+ if (holder in pendingRemoveAnimations) return
+ if (holder in pendingMoveAnimations) return
+
+ dispatchAnimationFinished(holder)
+ dispatchFinishedWhenDone()
+ }
+
+ private fun dispatchFinishedWhenDone() {
+ if (!isRunning) {
+ dispatchAnimationsFinished()
+ }
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemDecoration.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemDecoration.kt
new file mode 100644
index 0000000000..ec30ca0b92
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableItemDecoration.kt
@@ -0,0 +1,98 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.view.View
+import androidx.core.view.children
+import androidx.recyclerview.widget.ConcatAdapter
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import io.novafoundation.nova.common.utils.indexOfFirstOrNull
+import io.novafoundation.nova.common.utils.recyclerView.expandable.animator.ExpandableAnimator
+import io.novafoundation.nova.common.utils.recyclerView.expandable.animator.ExpandableAnimationItemState
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableBaseItem
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableChildItem
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableParentItem
+
+private data class ItemWithViewHolder(val position: Int, val item: ExpandableBaseItem, val viewHolder: ViewHolder?)
+
+abstract class ExpandableItemDecoration(
+ private val adapter: ExpandableAdapter,
+ private val animator: ExpandableAnimator
+) : RecyclerView.ItemDecoration() {
+
+ abstract fun onDrawGroup(
+ canvas: Canvas,
+ animationState: ExpandableAnimationItemState,
+ recyclerView: RecyclerView,
+ parentItem: ExpandableParentItem,
+ parent: ViewHolder?,
+ children: List
+ )
+
+ override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+ }
+
+ override fun onDraw(canvas: Canvas, recyclerView: RecyclerView, state: RecyclerView.State) {
+ val items = getParentAndChildren(recyclerView)
+ for ((parentItem, children) in items) {
+ val animationState = animator.getStateForPosition(parentItem.position) ?: continue
+ val childViewHolders = children.mapNotNull { it.viewHolder }
+ onDrawGroup(canvas, animationState, recyclerView, parentItem.item as ExpandableParentItem, parentItem.viewHolder, childViewHolders)
+ }
+ }
+
+ private fun getParentAndChildren(recyclerView: RecyclerView): Map> {
+ // Searching all view holders in recycler view and match them with adapter items
+ val items = recyclerView.children.toList()
+ .mapNotNull {
+ val viewHolder = recyclerView.getChildViewHolder(it)
+ val expandableViewHolder = viewHolder as? ExpandableBaseViewHolder<*> ?: return@mapNotNull null
+ val item = expandableViewHolder.expandableItem ?: return@mapNotNull null
+ ItemWithViewHolder(viewHolder.bindingAdapterPosition, item, viewHolder)
+ }
+
+ // Grouping view holders by parents
+ val parentsWithChildren = mutableMapOf>()
+
+ val parents = items.filter { it.item is ExpandableParentItem }.associateBy { it.item.getId() }
+ val children = items.filter { it.item is ExpandableChildItem }
+ parents.values.forEach { parentsWithChildren[it] = mutableListOf() }
+
+ children.forEach { child ->
+ val item = child.item as ExpandableChildItem
+ val parent = parents[item.groupId] ?: getParentForItem(recyclerView, item) ?: return@forEach
+ val parentChildren = parentsWithChildren[parent] ?: mutableListOf()
+ parentChildren.add(child)
+ parentsWithChildren[parent] = parentChildren
+ }
+
+ return parentsWithChildren
+ }
+
+ private fun getParentForItem(recyclerView: RecyclerView, item: ExpandableChildItem): ItemWithViewHolder? {
+ val positionInAdapter = adapter.getItems().indexOfFirstOrNull { it.getId() == item.groupId } ?: return null
+ val parentItem = adapter.getItems()[positionInAdapter]
+ val globalAdapterPosition = positionInAdapter.convertToGlobalAdapterPosition(recyclerView, adapter as Adapter<*>)
+ val viewHolder = recyclerView.findViewHolderForAdapterPosition(globalAdapterPosition)
+ return ItemWithViewHolder(positionInAdapter, parentItem as ExpandableParentItem, viewHolder)
+ }
+
+ // Useful to find global position if ConcatAdapter is used
+ private fun Int.convertToGlobalAdapterPosition(recyclerView: RecyclerView, localAdapter: Adapter<*>): Int {
+ val globalAdapter = recyclerView.adapter
+ return if (globalAdapter is ConcatAdapter) {
+ val localAdapterIndex = globalAdapter.adapters.indexOf(localAdapter)
+ if (localAdapterIndex > 0) {
+ val adaptersBeforeTarget = globalAdapter.adapters.subList(0, localAdapterIndex - 1)
+ val offset = adaptersBeforeTarget.sumOf { it.itemCount }
+ this + offset
+ } else {
+ this
+ }
+ } else {
+ this
+ }
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableParentViewHolder.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableParentViewHolder.kt
new file mode 100644
index 0000000000..503bbcb7b9
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/ExpandableParentViewHolder.kt
@@ -0,0 +1,21 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable
+
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableBaseItem
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableChildItem
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableParentItem
+
+interface ExpandableBaseViewHolder {
+ var expandableItem: T?
+}
+
+/**
+ * The view holder that may show ExpandableChildItem's
+ * It's used to check the type of viewHolder in [ExpandableItemDecoration]
+ */
+interface ExpandableParentViewHolder : ExpandableBaseViewHolder
+
+/**
+ * The view holder that is shown as an ExpandableChildItem
+ * It's used to check the type of viewHolder in [ExpandableItemDecoration]
+ */
+interface ExpandableChildViewHolder : ExpandableBaseViewHolder
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/animator/ExpandableAnimator.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/animator/ExpandableAnimator.kt
new file mode 100644
index 0000000000..eb54259080
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/animator/ExpandableAnimator.kt
@@ -0,0 +1,107 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable.animator
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import androidx.core.animation.addListener
+import androidx.recyclerview.widget.RecyclerView
+import io.novafoundation.nova.common.utils.recyclerView.expandable.ExpandableAdapter
+import io.novafoundation.nova.common.utils.recyclerView.expandable.ExpandableAnimationSettings
+import io.novafoundation.nova.common.utils.recyclerView.expandable.flippedFraction
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableBaseItem
+import io.novafoundation.nova.common.utils.recyclerView.expandable.items.ExpandableParentItem
+
+/**
+ * EXPANDING: animationFraction = 0f - fully collapsed and animationFraction = 1f - fully expanded
+ * COLLAPSING: animationFraction = 0f - fully expanded and animationFraction = 1f - fully collapsed
+ * So animationFraction is always move from 0f to 1f
+ */
+class ExpandableAnimationItemState(val animationType: Type, animationFraction: Float) {
+
+ var animationFraction: Float = animationFraction
+ internal set(value) {
+ field = value.coerceIn(0f, 1f)
+ }
+
+ enum class Type {
+ EXPANDING, COLLAPSING
+ }
+}
+
+private class RunningAnimation(val currentState: ExpandableAnimationItemState, val animator: Animator)
+
+class ExpandableAnimator(
+ private val recyclerView: RecyclerView,
+ private val animationSettings: ExpandableAnimationSettings,
+ private val expandableAdapter: ExpandableAdapter
+) {
+
+ // It contains only items that is animating right now
+ private val runningAnimations = mutableMapOf()
+
+ // Return current animation state for parent position or calculate state in [getExpandableItemState] if it isn't animating now
+ fun getStateForPosition(position: Int): ExpandableAnimationItemState? {
+ val items = expandableAdapter.getItems()
+ val item = items.getOrNull(position) ?: return null
+ if (item !is ExpandableParentItem) return null
+
+ return runningAnimations[item.getId()]?.currentState ?: getExpandableItemState(position, items)
+ }
+
+ // Just prepare an animation without running
+ fun prepareAnimationToState(parentId: String, type: ExpandableAnimationItemState.Type) {
+ val existingSettings = runningAnimations[parentId]
+
+ // No need to run animation if animation state is running and current type is the same
+ if (existingSettings == null) {
+ val state = ExpandableAnimationItemState(type, 0f)
+ setAnimationFor(parentId, state)
+ } else {
+ // No need to update animation state if it's the same and already running
+ if (existingSettings.currentState.animationType == type) {
+ return
+ }
+
+ // Toggle animation state and flipping fraction to continue the animation but to another side
+ val state = ExpandableAnimationItemState(type, existingSettings.currentState.flippedFraction())
+ setAnimationFor(parentId, state)
+ }
+ }
+
+ fun runAnimationFor(parentId: String) {
+ val existingSettings = runningAnimations[parentId]
+ existingSettings?.animator?.start()
+ }
+
+ private fun setAnimationFor(parentId: String, state: ExpandableAnimationItemState) {
+ runningAnimations[parentId]?.animator?.cancel() // Cancel previous animation if it's exist
+
+ val animator = ValueAnimator.ofFloat(state.animationFraction, 1f)
+ .setDuration(animationSettings.duration)
+
+ animator.interpolator = animationSettings.interpolator
+ animator.addUpdateListener {
+ state.animationFraction = it.animatedValue as Float
+ recyclerView.invalidate()
+ } // Invalidate recycler view to trigger onDraw in Item Decoration
+ animator.addListener(onEnd = { runningAnimations.remove(parentId) })
+
+ runningAnimations[parentId] = RunningAnimation(state, animator)
+ }
+
+ fun cancelAnimations() {
+ runningAnimations.values
+ .toList() // Copy list to avoid ConcurrentModificationException
+ .forEach { it.animator.cancel() }
+ }
+
+ private fun getExpandableItemState(position: Int, items: List): ExpandableAnimationItemState {
+ val nextItem = items.getOrNull(position + 1)
+
+ // If next item is not a parent item it means current item is fully expanded
+ return if (nextItem == null || nextItem is ExpandableParentItem) {
+ ExpandableAnimationItemState(ExpandableAnimationItemState.Type.COLLAPSING, 1f)
+ } else {
+ ExpandableAnimationItemState(ExpandableAnimationItemState.Type.EXPANDING, 1f)
+ }
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableBaseItem.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableBaseItem.kt
new file mode 100644
index 0000000000..2bcd71970e
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableBaseItem.kt
@@ -0,0 +1,8 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable.items
+
+/**
+ * The item that may show ExpandingItem's
+ */
+interface ExpandableBaseItem {
+ fun getId(): String
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableChildItem.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableChildItem.kt
new file mode 100644
index 0000000000..902d8a1e72
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableChildItem.kt
@@ -0,0 +1,9 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable.items
+
+/**
+ * The item that may be shown or hidden From ExpandableItem
+ */
+interface ExpandableChildItem : ExpandableBaseItem {
+
+ val groupId: String
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableParentItem.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableParentItem.kt
new file mode 100644
index 0000000000..dee85b835e
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/expandable/items/ExpandableParentItem.kt
@@ -0,0 +1,6 @@
+package io.novafoundation.nova.common.utils.recyclerView.expandable.items
+
+/**
+ * The item that may show ExpandingItem's
+ */
+interface ExpandableParentItem : ExpandableBaseItem
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/AmountView.kt b/common/src/main/java/io/novafoundation/nova/common/view/AmountView.kt
index 1f06343917..2cb0e6491a 100644
--- a/common/src/main/java/io/novafoundation/nova/common/view/AmountView.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/view/AmountView.kt
@@ -8,9 +8,10 @@ import android.view.View
import android.widget.EditText
import androidx.constraintlayout.widget.ConstraintLayout
import coil.ImageLoader
-import coil.load
import io.novafoundation.nova.common.R
import io.novafoundation.nova.common.di.FeatureUtils
+import io.novafoundation.nova.common.utils.images.Icon
+import io.novafoundation.nova.common.utils.images.setIcon
import io.novafoundation.nova.common.utils.setTextOrHide
import io.novafoundation.nova.common.view.shape.getBlockDrawable
import io.novafoundation.nova.common.view.shape.getCornersStateDrawable
@@ -87,8 +88,8 @@ class AmountView @JvmOverloads constructor(
stakingAssetImage.setImageDrawable(image)
}
- fun loadAssetImage(imageUrl: String?) {
- stakingAssetImage.load(imageUrl, imageLoader)
+ fun loadAssetImage(icon: Icon) {
+ stakingAssetImage.setIcon(icon, imageLoader)
}
fun setAssetName(name: String) {
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/IconButton.kt b/common/src/main/java/io/novafoundation/nova/common/view/IconButton.kt
index aae822e54f..3a73e6bf75 100644
--- a/common/src/main/java/io/novafoundation/nova/common/view/IconButton.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/view/IconButton.kt
@@ -8,6 +8,7 @@ import io.novafoundation.nova.common.R
import io.novafoundation.nova.common.utils.WithContextExtensions
import io.novafoundation.nova.common.utils.updatePadding
import io.novafoundation.nova.common.utils.useAttributes
+import io.novafoundation.nova.common.view.shape.getMaskedRipple
class IconButton @JvmOverloads constructor(
context: Context,
@@ -18,7 +19,7 @@ class IconButton @JvmOverloads constructor(
init {
updatePadding(top = 6.dp, bottom = 6.dp, start = 12.dp, end = 12.dp)
- background = addRipple(getRoundedCornerDrawable(R.color.button_background_secondary))
+ background = context.getMaskedRipple(cornerSizeInDp = 10)
attrs?.let(::applyAttributes)
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/QrCodeView.kt b/common/src/main/java/io/novafoundation/nova/common/view/QrCodeView.kt
new file mode 100644
index 0000000000..af48b0b7a5
--- /dev/null
+++ b/common/src/main/java/io/novafoundation/nova/common/view/QrCodeView.kt
@@ -0,0 +1,189 @@
+package io.novafoundation.nova.common.view
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.graphics.toRect
+import coil.ImageLoader
+import coil.request.ImageRequest
+import com.google.zxing.qrcode.encoder.QRCode
+import io.novafoundation.nova.common.R
+import io.novafoundation.nova.common.di.FeatureUtils
+import io.novafoundation.nova.common.utils.dp
+import io.novafoundation.nova.common.utils.dpF
+import io.novafoundation.nova.common.utils.images.Icon
+import io.novafoundation.nova.common.utils.images.formatIcon
+
+class QrCodeModel(
+ val qrCode: QRCode,
+ val overlayBackground: Drawable?,
+ val overlayPaddingInDp: Int,
+ val centerOverlay: Icon,
+)
+
+class QrCodeView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ private val qrColor = context.getColor(R.color.qr_code_content)
+ private val backgroundColor = context.getColor(R.color.qr_code_background)
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+
+ private var data: QRCode? = null
+
+ private var overlayPadding: Int = 0
+ private var centerOverlay: Drawable? = null
+ private var overlayBackground: Drawable? = null
+
+ private val overlaySize = 64.dp
+ private val overlayQuiteZone = 0
+ private val qrPadding = 16.dpF
+
+ private val centerRect: RectF = RectF()
+
+ private val imageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) {
+ if (isInEditMode) {
+ ImageLoader.invoke(context)
+ } else {
+ FeatureUtils.getCommonApi(context).imageLoader()
+ }
+ }
+
+ fun setQrModel(model: QrCodeModel) {
+ this.data = model.qrCode
+ this.overlayBackground = model.overlayBackground
+ this.overlayPadding = model.overlayPaddingInDp.dp(context)
+
+ if (model.centerOverlay != null) {
+ val centerOverlayRequest = getCenterOverlayImageRequest(model.centerOverlay) {
+ this.centerOverlay = it
+ invalidate()
+ }
+
+ imageLoader.enqueue(centerOverlayRequest)
+ }
+ invalidate()
+ }
+
+ private fun getCenterOverlayImageRequest(icon: Icon, target: (Drawable?) -> Unit): ImageRequest {
+ return ImageRequest.Builder(context)
+ .data(ImageLoader.formatIcon(icon))
+ .target(onSuccess = target)
+ .size(overlaySize, overlaySize)
+ .build()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val heightSize = MeasureSpec.getSize(heightMeasureSpec)
+
+ setMeasuredDimension(widthSize, heightSize)
+
+ centerRect.left = (measuredWidth / 2 - overlaySize / 2).toFloat()
+ centerRect.top = (measuredHeight / 2 - overlaySize / 2).toFloat()
+ centerRect.right = (measuredWidth / 2 + overlaySize / 2).toFloat()
+ centerRect.bottom = (measuredHeight / 2 + overlaySize / 2).toFloat()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ canvas.save()
+
+ paint.color = qrColor
+ canvas.drawColor(backgroundColor)
+ data?.let { renderQRCodeImage(canvas, it) }
+
+ canvas.restore()
+ }
+
+ private fun renderQRCodeImage(canvas: Canvas, data: QRCode) {
+ renderQRImage(canvas, data, width, height)
+ }
+
+ private fun renderQRImage(canvas: Canvas, code: QRCode, width: Int, height: Int) {
+ paint.color = qrColor
+ val input = code.matrix ?: throw IllegalStateException()
+ val inputWidth = input.width
+ val inputHeight = input.height
+ val outputWidth = Math.max(width, inputWidth) - qrPadding * 2
+ val outputHeight = Math.max(height, inputHeight) - qrPadding * 2
+ val multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight)
+ val leftPadding = qrPadding
+ val topPadding = qrPadding
+ val FINDER_PATTERN_SIZE = 7f
+ val CIRCLE_SCALE_DOWN_FACTOR = 0.7f
+ val circleSize = (multiple * CIRCLE_SCALE_DOWN_FACTOR)
+ val circleRadius = circleSize / 2
+
+ var inputY = 0
+ var outputY = topPadding
+
+ while (inputY < inputHeight) {
+ var inputX = 0
+ var outputX = leftPadding
+ while (inputX < inputWidth) {
+ if (input[inputX, inputY].toInt() == 1) {
+ val overlaysFinder = inputX <= FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
+ inputX >= inputWidth - FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
+ inputX <= FINDER_PATTERN_SIZE && inputY >= inputHeight - FINDER_PATTERN_SIZE
+
+ val overlaysCenter = (this.overlay != null) && (
+ this.centerRect.intersects(
+ outputX - overlayQuiteZone,
+ outputY - overlayQuiteZone,
+ outputX + circleSize + overlayQuiteZone,
+ outputY + circleRadius + overlayQuiteZone
+ )
+ )
+
+ if (!overlaysCenter && !overlaysFinder) {
+ canvas.drawCircle(paddingStart + outputX + circleRadius, paddingStart + outputY + circleRadius, circleRadius, paint)
+ }
+ }
+ inputX++
+ outputX += multiple
+ }
+ inputY++
+ outputY += multiple
+ }
+ val cornerCircleDiameter = multiple * FINDER_PATTERN_SIZE
+ drawFinderPatternCircleStyle(canvas, leftPadding, topPadding, cornerCircleDiameter)
+ drawFinderPatternCircleStyle(canvas, leftPadding + (inputWidth - FINDER_PATTERN_SIZE) * multiple, topPadding, cornerCircleDiameter)
+ drawFinderPatternCircleStyle(canvas, leftPadding, topPadding + (inputHeight - FINDER_PATTERN_SIZE) * multiple, cornerCircleDiameter)
+
+ drawCenterOverlay(canvas)
+ }
+
+ private fun drawFinderPatternCircleStyle(canvas: Canvas, x: Float, y: Float, circleDiameter: Float) {
+ val radius = circleDiameter / 2
+ val WHITE_CIRCLE_RADIUS = circleDiameter * 5 / 7 / 2
+ val WHITE_CIRCLE_OFFSET = circleDiameter / 7 + WHITE_CIRCLE_RADIUS
+ val MIDDLE_DOT_RADIUS = circleDiameter * 3 / 7 / 2
+ val MIDDLE_DOT_OFFSET = circleDiameter * 2 / 7 + MIDDLE_DOT_RADIUS
+ paint.color = qrColor
+ canvas.drawCircle(x + radius, y + radius, radius, paint)
+ paint.color = backgroundColor
+ canvas.drawCircle(x + WHITE_CIRCLE_OFFSET, y + WHITE_CIRCLE_OFFSET, WHITE_CIRCLE_RADIUS, paint)
+ paint.color = qrColor
+ canvas.drawCircle(x + MIDDLE_DOT_OFFSET, y + MIDDLE_DOT_OFFSET, MIDDLE_DOT_RADIUS, paint)
+ }
+
+ private fun drawCenterOverlay(canvas: Canvas) {
+ val overlayBackgroundWithInsets = centerRect.toRect().apply {
+ inset(overlayPadding, overlayPadding)
+ }
+
+ overlayBackground?.bounds = overlayBackgroundWithInsets
+ centerOverlay?.bounds = overlayBackgroundWithInsets
+
+ overlayBackground?.draw(canvas)
+ centerOverlay?.draw(canvas)
+ }
+}
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt
index 4da2908487..b87c1a8ff6 100644
--- a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt
@@ -195,7 +195,7 @@ open class TableCellView @JvmOverloads constructor(
tableCellTitle.setDrawableStart(icon, widthInDp = 16, paddingInDp = 4, tint = tintRes)
}
- fun showValue(primary: CharSequence, secondary: String? = null) {
+ fun showValue(primary: CharSequence, secondary: CharSequence? = null) {
postToSelf {
contentGroup.makeVisible()
@@ -278,7 +278,7 @@ open class TableCellView @JvmOverloads constructor(
}
}
-fun TableCellView.showValueOrHide(primary: CharSequence?, secondary: String? = null) {
+fun TableCellView.showValueOrHide(primary: CharSequence?, secondary: CharSequence? = null) {
if (primary != null) {
showValue(primary, secondary)
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/list/dynamic/DynamicListBottomSheet.kt b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/list/dynamic/DynamicListBottomSheet.kt
index da5bd6d575..4dd6b54d50 100644
--- a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/list/dynamic/DynamicListBottomSheet.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/list/dynamic/DynamicListBottomSheet.kt
@@ -11,12 +11,14 @@ import androidx.recyclerview.widget.RecyclerView
import io.novafoundation.nova.common.R
import io.novafoundation.nova.common.utils.DialogExtensions
import io.novafoundation.nova.common.utils.WithContextExtensions
+import io.novafoundation.nova.common.utils.setTextOrHide
import io.novafoundation.nova.common.utils.setVisible
import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet
import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetContent
import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetHeader
import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetItemContainer
import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetRightAction
+import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetSubtitle
import kotlinx.android.synthetic.main.bottom_sheet_dynamic_list.dynamicListSheetTitle
typealias ClickHandler = (BaseDynamicListBottomSheet, T) -> Unit
@@ -56,6 +58,10 @@ abstract class BaseDynamicListBottomSheet(context: Context) :
dynamicListSheetTitle.text = title
}
+ fun setSubtitle(subtitle: CharSequence?) {
+ dynamicListSheetSubtitle.setTextOrHide(subtitle)
+ }
+
final override fun setTitle(titleId: Int) {
dynamicListSheetTitle.setText(titleId)
}
diff --git a/common/src/main/java/io/novafoundation/nova/common/view/recyclerview/item/OperationListItem.kt b/common/src/main/java/io/novafoundation/nova/common/view/recyclerview/item/OperationListItem.kt
index 7a00301eaf..a5f76b4294 100644
--- a/common/src/main/java/io/novafoundation/nova/common/view/recyclerview/item/OperationListItem.kt
+++ b/common/src/main/java/io/novafoundation/nova/common/view/recyclerview/item/OperationListItem.kt
@@ -60,7 +60,6 @@ class OperationListItem @kotlin.jvm.JvmOverloads constructor(
fun setIconStyle(iconStyle: IconStyle) {
when (iconStyle) {
IconStyle.BORDERED_CIRCLE -> {
- icon.setPadding(6.dp)
icon.setBackgroundResource(R.drawable.bg_icon_container_on_color)
icon.setImageTintRes(R.color.icon_secondary)
}
diff --git a/common/src/main/res/anim/asset_mode_fade_in.xml b/common/src/main/res/anim/asset_mode_fade_in.xml
new file mode 100644
index 0000000000..33a1e91d0d
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_fade_in.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/anim/asset_mode_fade_out.xml b/common/src/main/res/anim/asset_mode_fade_out.xml
new file mode 100644
index 0000000000..a7dccf1656
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_fade_out.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/anim/asset_mode_slide_bottom_in.xml b/common/src/main/res/anim/asset_mode_slide_bottom_in.xml
new file mode 100644
index 0000000000..42fca22211
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_slide_bottom_in.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/anim/asset_mode_slide_bottom_out.xml b/common/src/main/res/anim/asset_mode_slide_bottom_out.xml
new file mode 100644
index 0000000000..d6e7826cac
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_slide_bottom_out.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/anim/asset_mode_slide_top_in.xml b/common/src/main/res/anim/asset_mode_slide_top_in.xml
new file mode 100644
index 0000000000..f17339bd58
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_slide_top_in.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/anim/asset_mode_slide_top_out.xml b/common/src/main/res/anim/asset_mode_slide_top_out.xml
new file mode 100644
index 0000000000..702bd58091
--- /dev/null
+++ b/common/src/main/res/anim/asset_mode_slide_top_out.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/color/appearance_selectable_text.xml b/common/src/main/res/color/appearance_selectable_text.xml
new file mode 100644
index 0000000000..0eff9797cc
--- /dev/null
+++ b/common/src/main/res/color/appearance_selectable_text.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/bg_appearance_container.xml b/common/src/main/res/drawable/bg_appearance_container.xml
new file mode 100644
index 0000000000..35e891d176
--- /dev/null
+++ b/common/src/main/res/drawable/bg_appearance_container.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/bg_primary_list_item_corner_12.xml b/common/src/main/res/drawable/bg_primary_list_item_corner_12.xml
new file mode 100644
index 0000000000..70b4025ffd
--- /dev/null
+++ b/common/src/main/res/drawable/bg_primary_list_item_corner_12.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/bg_primary_list_item_corner_12_solid.xml b/common/src/main/res/drawable/bg_primary_list_item_corner_12_solid.xml
new file mode 100644
index 0000000000..ca2c3db466
--- /dev/null
+++ b/common/src/main/res/drawable/bg_primary_list_item_corner_12_solid.xml
@@ -0,0 +1,19 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/bg_token_container.xml b/common/src/main/res/drawable/bg_token_container.xml
index 26cb5eb585..8953dac9b2 100644
--- a/common/src/main/res/drawable/bg_token_container.xml
+++ b/common/src/main/res/drawable/bg_token_container.xml
@@ -1,10 +1,25 @@
-
+
+ -
+
-
+
-
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
diff --git a/common/src/main/res/drawable/ic_asset_view_networks.xml b/common/src/main/res/drawable/ic_asset_view_networks.xml
new file mode 100644
index 0000000000..d96811e9b4
--- /dev/null
+++ b/common/src/main/res/drawable/ic_asset_view_networks.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/common/src/main/res/drawable/ic_asset_view_tokens.xml b/common/src/main/res/drawable/ic_asset_view_tokens.xml
new file mode 100644
index 0000000000..b4a235b52e
--- /dev/null
+++ b/common/src/main/res/drawable/ic_asset_view_tokens.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/common/src/main/res/drawable/ic_nova.xml b/common/src/main/res/drawable/ic_nova.xml
index 7c3883474a..3509569d13 100644
--- a/common/src/main/res/drawable/ic_nova.xml
+++ b/common/src/main/res/drawable/ic_nova.xml
@@ -1,9 +1,11 @@
-
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+
+
diff --git a/common/src/main/res/drawable/ic_receive_history.xml b/common/src/main/res/drawable/ic_receive_history.xml
new file mode 100644
index 0000000000..6523824cb6
--- /dev/null
+++ b/common/src/main/res/drawable/ic_receive_history.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/common/src/main/res/drawable/ic_send_history.xml b/common/src/main/res/drawable/ic_send_history.xml
new file mode 100644
index 0000000000..255dc6da7d
--- /dev/null
+++ b/common/src/main/res/drawable/ic_send_history.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/common/src/main/res/drawable/ic_settings_appearance.xml b/common/src/main/res/drawable/ic_settings_appearance.xml
new file mode 100644
index 0000000000..e9ff47a09b
--- /dev/null
+++ b/common/src/main/res/drawable/ic_settings_appearance.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/common/src/main/res/drawable/ic_staking_history.xml b/common/src/main/res/drawable/ic_staking_history.xml
new file mode 100644
index 0000000000..e9874f87b4
--- /dev/null
+++ b/common/src/main/res/drawable/ic_staking_history.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/common/src/main/res/drawable/ic_swap_history.xml b/common/src/main/res/drawable/ic_swap_history.xml
new file mode 100644
index 0000000000..405a5488c7
--- /dev/null
+++ b/common/src/main/res/drawable/ic_swap_history.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/common/src/main/res/drawable/ic_token_dot.xml b/common/src/main/res/drawable/ic_token_dot.xml
deleted file mode 100644
index 90b21487ca..0000000000
--- a/common/src/main/res/drawable/ic_token_dot.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
diff --git a/common/src/main/res/drawable/ic_token_dot_colored.xml b/common/src/main/res/drawable/ic_token_dot_colored.xml
new file mode 100644
index 0000000000..6bf4dc417f
--- /dev/null
+++ b/common/src/main/res/drawable/ic_token_dot_colored.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/common/src/main/res/drawable/ic_token_dot_white.xml b/common/src/main/res/drawable/ic_token_dot_white.xml
new file mode 100644
index 0000000000..d968154d4c
--- /dev/null
+++ b/common/src/main/res/drawable/ic_token_dot_white.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/common/src/main/res/layout/item_operation_list_item.xml b/common/src/main/res/layout/item_operation_list_item.xml
index 7f06bfdb39..11dcd20dc5 100644
--- a/common/src/main/res/layout/item_operation_list_item.xml
+++ b/common/src/main/res/layout/item_operation_list_item.xml
@@ -14,10 +14,11 @@
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
+ android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:src="@drawable/ic_staking_filled" />
+ tools:src="@drawable/ic_send_history" />
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@drawable/ic_token_dot_colored"
+ tools:visibility="visible" />
Has alcanzado el límite de %s proxies añadidos en %s. Elimina proxies para añadir nuevos.
Se ha alcanzado el número máximo de proxies
Las redes personalizadas agregadas\naquí aparecerán
+ Coloreado
+ Apariencia
+ iconos de token
+ Blanco
La dirección de contrato ingresada ya está presente en Nova como un token %s.
La dirección de contrato ingresada ya está presente en Nova como un token %s. ¿Estás seguro de que quieres modificarlo?
Este token ya existe
@@ -185,6 +189,8 @@
Ingrese la dirección del contrato
Ingrese los decimales
Ingrese el símbolo
+ Redes
+ Tokens
Agregar token
Dirección del contrato
Decimales
@@ -207,6 +213,7 @@
Ledger no admite este token
Buscar por red o token
No se encontraron redes o tokens con\n el nombre ingresado
+ Buscar por token
Sus carteras
No tiene tokens para enviar.\nCompre o reciba tokens en su\ncuenta.
Token para pagar
@@ -235,6 +242,7 @@
Respaldar
Autenticación biométrica
¡Compra iniciada! Por favor, espere hasta 60 minutos. Puede seguir el estado en el correo electrónico.
+ Seleccionar red para comprar %s
Para continuar la compra será redirigido desde la aplicación Nova Wallet a %s
¿Continuar en el navegador?
Equilibrar nodos automáticamente
@@ -968,6 +976,8 @@
Al habilitar las notificaciones push, acepta nuestros %s y %s
Intente de nuevo más tarde accediendo a los ajustes de notificación desde la pestaña de Configuración
¡No te pierdas de nada!
+ Seleccionar red para recibir %s
+ Copiar dirección
Pega el json o sube un archivo…
Subir archivo
Restaurar JSON
@@ -1122,9 +1132,11 @@
Pistas no disponibles
Nova necesita que la ubicación esté habilitada para poder realizar el escaneo bluetooth y encontrar tu dispositivo Ledger
Por favor, habilita la geolocalización en las configuraciones del dispositivo
+ Seleccionar red
Seleccione pistas para
%d de %d
Dirección o w3n
+ Seleccionar red para enviar %s
El destinatario es una cuenta del sistema. No está controlado por ninguna empresa o individuo.\n¿Estás seguro de que aún quieres realizar esta transferencia?
Los tokens se perderán
Otorgar autoridad a
@@ -1534,6 +1546,7 @@
Ingresa otro monto
Para pagar la comisión de red con %s, Nova intercambiará automáticamente %s por %s para mantener el balance mínimo de %s en tu cuenta.
Una comisión de red cobrada por la cadena de bloques para procesar y validar cualquier transacción. Puede variar dependiendo de las condiciones de la red o la velocidad de la transacción.
+ Seleccionar red para intercambiar %s
El fondo no tiene suficiente liquidez para intercambiar
La diferencia de precio se refiere a la diferencia en el precio entre dos activos diferentes. Al realizar un intercambio en cripto, la diferencia de precio es generalmente la diferencia entre el precio del activo por el que estás intercambiando y el precio del activo con el que estás intercambiando.
Diferencia de precio
@@ -1626,6 +1639,7 @@
Comprar con
Recibir
Recibir %s
+ Enviar solo el token %1$s y tokens en la red %2$s a esta dirección, o podrías perder tus fondos
Enviar
Intercambiar
Activos
diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml
index df5fbce85c..1a2d369044 100644
--- a/common/src/main/res/values-fr-rFR/strings.xml
+++ b/common/src/main/res/values-fr-rFR/strings.xml
@@ -173,6 +173,10 @@
Vous avez atteint la limite de %s proxies ajoutés dans %s. Supprimez des proxies pour en ajouter de nouveaux.
Le nombre maximum de proxies a été atteint
Les réseaux personnalisés ajoutés\napparaîtront ici
+ Coloré
+ Apparence
+ icônes des tokens
+ Blanc
L\'adresse du contrat saisie est présente dans Nova sous la forme d\'un jeton %s.
L\'adresse du contrat saisie est présente dans Nova comme un token %s. Êtes-vous sûr de vouloir le modifier ?
Ce jeton existe déjà
@@ -185,6 +189,8 @@
Entrez l\'adresse du contrat
Entrez les décimales
Entrez le symbole
+ Réseaux
+ Tokens
Ajouter un jeton
Adresse du contrat
Décimales
@@ -207,6 +213,7 @@
Ledger ne prend pas en charge cet actif
Recherche par réseau ou actif
Aucun réseau ou jeton avec\nnom saisi n\'a été trouvé
+ Rechercher par token
Vos portefeuilles
Vous n\'avez pas de tokens à envoyer.\nAchetez ou recevez des tokens sur votre\ncompte.
Token à payer
@@ -235,6 +242,7 @@
Sauvegarde
Auth biométrique
L\'achat est lancé ! Veuillez patienter jusqu\'à 60 minutes. Vous pouvez suivre l\'état d\'avancement sur l\'e-mail.
+ Sélectionner le réseau pour acheter %s
Pour poursuivre l\'achat, vous serez redirigé de l\'application Nova Wallet vers %s
Continuer dans le navigateur?
Équilibrage automatique des nœuds
@@ -968,6 +976,8 @@
En activant les notifications push, vous acceptez nos %s et %s
Veuillez réessayer plus tard en accédant aux paramètres de notification depuis l\'onglet Paramètres
Ne manquez rien !
+ Sélectionner le réseau pour recevoir %s
+ Copier l\'adresse
Collez le fichier JSON ou téléchargez le fichier...
Télécharger le fichier
Restaurer JSON
@@ -1122,9 +1132,11 @@
Chemins non disponibles
Nova a besoin que la localisation soit activée pour pouvoir effectuer un balayage Bluetooth afin de trouver votre appareil Ledger
Veuillez activer la géolocalisation dans les paramètres de l\'appareil
+ Sélectionner un réseau
Sélectionner des pistes pour
%d sur %d
Adresse ou w3n
+ Sélectionner le réseau pour envoyer %s
Le destinataire est un compte système. Il n\'est contrôlé par aucune entreprise ou individu.\nÊtes-vous sûr de vouloir effectuer ce transfert ?
Les tokens seront perdus
Donner l\'autorité à
@@ -1534,6 +1546,7 @@
Entrez un autre montant
Pour payer les frais de réseau avec %s, Nova échangera automatiquement %s contre %s pour maintenir le solde minimum de %s sur votre compte.
Des frais de réseau facturés par la blockchain pour traiter et valider toutes les transactions. Ils peuvent varier en fonction des conditions du réseau ou de la rapidité de la transaction.
+ Sélectionner le réseau pour échanger %s
Le pool n\'a pas assez de liquidité pour échanger
La différence de prix fait référence à la différence de prix entre deux actifs différents. Lors de l\'échange en crypto, la différence de prix est généralement la différence entre le prix de l\'actif que vous échangez et le prix de l\'actif avec lequel vous échangez.
Différence de prix
@@ -1626,6 +1639,7 @@
Acheter avec
Recevoir
Recevoir %s
+ Envoyez uniquement le token %1$s et les tokens dans le réseau %2$s à cette adresse, ou vous pourriez perdre vos fonds
Envoyer
Échanger
Actifs
diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml
index 2c83b7e843..d6b692fefb 100644
--- a/common/src/main/res/values-in/strings.xml
+++ b/common/src/main/res/values-in/strings.xml
@@ -173,6 +173,10 @@
Anda telah mencapai batas %s proxy yang ditambahkan dalam %s. Hapus proxy untuk menambahkan yang baru.
Jumlah maksimum proxy telah tercapai
Jaringan kustom yang ditambahkan\nakan muncul di sini
+ Berwarna
+ Penampilan
+ ikon token
+ Putih
Alamat kontrak yang dimasukkan ada dalam Nova sebagai token %s.
Alamat kontrak yang dimasukkan ada dalam Nova sebagai token %s. Apakah Anda yakin ingin memodifikasinya?
Token ini sudah ada
@@ -185,6 +189,8 @@
Masukkan alamat kontrak
Masukkan desimal
Masukkan simbol
+ Jaringan
+ Token
Tambahkan token
Alamat kontrak
Desimal
@@ -207,6 +213,7 @@
Ledger tidak mendukung token ini
Cari berdasarkan jaringan atau token
Tidak ada jaringan atau token dengan nama yang dimasukkan ditemukan
+ Cari berdasarkan token
Dompet Anda
Anda tidak memiliki token untuk dikirim. Beli atau Terima token ke akun Anda.
Token untuk membayar
@@ -235,6 +242,7 @@
Cadangan
Otentikasi Biometrik
Pembelian dimulai! Harap tunggu hingga 60 menit. Anda dapat melacak statusnya melalui email.
+ Pilih jaringan untuk membeli %s
Untuk melanjutkan pembelian, Anda akan diarahkan dari aplikasi Nova Wallet ke %s
Lanjut di browser?
Node saldo otomatis
@@ -961,6 +969,8 @@
Dengan mengaktifkan pemberitahuan push, Anda menyetujui %s dan %s kami
Harap coba lagi nanti dengan mengakses pengaturan pemberitahuan dari tab Pengaturan
Jangan lewatkan hal apa pun!
+ Pilih jaringan untuk menerima %s
+ Salin Alamat
Tempelkan json atau unggah file...
Unggah file
Pulihkan JSON
@@ -1114,9 +1124,11 @@
Unavailable tracks
Nova needs location to be enabled to be able to perform bluetooth scanning to find your Ledger device
Please enable geo-location in device settings
+ Pilih jaringan
Select tracks for
%d of %d
Address or w3n
+ Pilih jaringan untuk mengirim %s
Recipient is a system account. It is not controlled by any company or individual.\nAre you sure you still want to perform this transfer?
Tokens will be lost
Give authority to
@@ -1524,6 +1536,7 @@
Masukkan jumlah lain
Untuk membayar biaya jaringan dengan %s, Nova secara otomatis akan menukar %s dengan %s untuk mempertahankan saldo minimum %s akun Anda.
Biaya jaringan yang dibebankan oleh blockchain untuk memproses dan memvalidasi transaksi. Bisa bervariasi tergantung pada kondisi jaringan atau kecepatan transaksi.
+ Pilih jaringan untuk menukar %s
Pool tidak memiliki likuiditas yang cukup untuk swap
Perbedaan harga merujuk pada perbedaan harga antara dua aset yang berbeda. Ketika melakukan swap dalam kripto, perbedaan harga biasanya adalah perbedaan antara harga aset yang Anda tukarkan dengan harga aset yang Anda tukar.
Perbedaan harga
@@ -1616,6 +1629,7 @@
Beli dengan
Terima
Terima %s
+ Hanya kirim token %1$s dan token di jaringan %2$s ke alamat ini, atau Anda bisa kehilangan dana Anda
Kirim
Tukar
Aset
diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml
index df030d8362..7ccfbe525a 100644
--- a/common/src/main/res/values-it/strings.xml
+++ b/common/src/main/res/values-it/strings.xml
@@ -173,6 +173,10 @@
Hai raggiunto il limite di %s proxy aggiunti in %s. Rimuovi i proxy per aggiungerne di nuovi
Limite massimo di proxy raggiunto
Le reti personalizzate aggiunte\nappariranno qui
+ Colorato
+ Aspetto
+ icone dei token
+ Bianco
L\'indirizzo del contratto inserito è presente in Nova come token %s.
L\'indirizzo del contratto inserito è presente in Nova come token %s. Sei sicuro di volerlo modificare?
Questo token esiste già
@@ -185,6 +189,8 @@
Inserisci l\'indirizzo del contratto
Inserisci i decimali
Inserisci il simbolo
+ Reti
+ Token
Aggiungi token
Indirizzo del contratto
Decimali
@@ -207,6 +213,7 @@
Ledger non supporta questo token
Cerca per rete o token
Nessuna rete o token con il nome inserito è stata trovata
+ Cerca per token
I tuoi portafogli
Non hai token da inviare. \nCompra o ricevi token sul tuo \nconto.
Token da pagare
@@ -235,6 +242,7 @@
Backup
Autenticazione biometrica
Acquisto iniziato! Attendi fino a 60 minuti. Puoi controllare lo stato sulla email.
+ Seleziona la rete per l\'acquisto di %s
Per continuare l\'acquisto sarai reindirizzato dall\'app Nova Wallet a %s
Continuare nel browser?
Bilanciamento automatico dei nodi
@@ -968,6 +976,8 @@
Abilitando le notifiche push, accetti i nostri %s e %s
Si prega di riprovare più tardi accedendo alle impostazioni delle notifiche dalla scheda Impostazioni
Non perdere nulla!
+ Seleziona la rete per ricevere %s
+ Copia indirizzo
Incolla json o carica il file...
Carica file
Ripristina JSON per il recupero
@@ -1122,9 +1132,11 @@
Tracce non disponibili
Nova ha bisogno che la posizione sia attivata per poter effettuare la scansione Bluetooth per trovare il tuo dispositivo Ledger
Si prega di abilitare la geolocalizzazione nelle impostazioni del dispositivo
+ Seleziona rete
Seleziona brani per
%d di %d
Indirizzo o w3n
+ Seleziona la rete per inviare %s
Il destinatario è un conto di sistema. Non è controllato da alcuna azienda o individuo.\nSei sicuro di voler comunque effettuare questo trasferimento?
I token verranno persi
Conferire autorità a
@@ -1534,6 +1546,7 @@
Inserisci un altro importo
Per pagare la tariffa di rete con %s, Nova effettuerà automaticamente lo scambio di %s per %s per mantenere il saldo minimo del tuo account di %s.
Una tariffa di rete addebitata dal blockchain per elaborare e convalidare qualsiasi transazione. Può variare a seconda delle condizioni di rete o della velocità delle transazioni.
+ Seleziona la rete per scambiare %s
Il pool non ha abbastanza liquidità per lo scambio
La differenza di prezzo si riferisce alla differenza di prezzo tra due diversi asset. Quando si effettua uno scambio in criptovaluta, la differenza di prezzo è di solito la differenza tra il prezzo dell\'asset che stai scambiando e il prezzo dell\'asset con cui stai scambiando.
Differenza di prezzo
@@ -1626,6 +1639,7 @@
Acquista con
Ricevi
Ricevi %s
+ Invia solo token %1$s e token nella rete %2$s a questo indirizzo, altrimenti potresti perdere i tuoi fondi
Invia
Scambia
Asset
diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml
index afb5a07e86..2b31a4ecfd 100644
--- a/common/src/main/res/values-ja/strings.xml
+++ b/common/src/main/res/values-ja/strings.xml
@@ -173,6 +173,10 @@
%sで追加できるプロキシの上限に達しました。新しいものを追加するためには既存のプロキシを削除してください。
プロキシの最大数に達しました
追加されたカスタムネットワーク\nここに表示されます
+ カラー
+ 外観
+ トークンアイコン
+ ホワイト
入力されたコントラクトアドレスは%sトークンとしてNovaに存在します。
入力されたコントラクトアドレスは%sトークンとしてNovaに存在します。本当にそれを変更しますか?
このトークンはすでに存在します
@@ -185,6 +189,8 @@
コントラクトアドレスを入力
小数を入力
シンボルを入力
+ ネットワーク
+ トークン
トークンを追加
コントラクトアドレス
小数
@@ -207,6 +213,7 @@
Ledgerはこのトークンをサポートしていません
ネットワークまたはトークンで検索
入力した名前のネットワークやトークンが見つかりませんでした
+ トークンで検索
あなたのウォレット
送信するトークンがありません。\nトークンを購入または受け取ってください\nアカウントに。
支払い用トークン
@@ -235,6 +242,7 @@
バックアップ
生体認証
購入が開始されました! 最大60分お待ちください。メールでステータスを追跡できます。
+ 購入するネットワークを選択 %s
購入を続行するために、Nova Walletアプリから%sにリダイレクトされます。
ブラウザで続行しますか?
自動バランスノード
@@ -961,6 +969,8 @@
プッシュ通知を有効にすることで、%sと%sに同意したことになります
設定タブから通知設定にアクセスして、後でもう一度お試しください
お見逃しなく!
+ 受信するネットワークを選択 %s
+ 住所をコピー
jsonを貼り付けるかファイルをアップロード...
ファイルをアップロード
JSONを復元
@@ -1114,9 +1124,11 @@
利用不可のトラック
Novaは、Ledgerデバイスを見つけるためにBluetoothスキャンを実行するために位置情報の有効化が必要です
デバイス設定で位置情報を有効にしてください
+ ネットワークを選択
トラックを選択
%d / %d
アドレスまたはw3n
+ 送信するネットワークを選択 %s
受信者はシステムアカウントです。どの会社や個人にも管理されていません。\nそれでもこの転送を実行してもよろしいですか?
トークンが失われます
権限を与える
@@ -1524,6 +1536,7 @@
別の金額を入力
ネットワーク手数料を %s で支払うため、Novaは %s を %s に自動的にスワップして、アカウントの最低 %s 残高を維持します。
ブロックチェーンによって取引や検証を処理するためのネットワーク手数料。ネットワークの状況や取引速度によって異なる場合があります。
+ スワップするネットワークを選択 %s
プールにスワップするための十分な流動性がありません
価格差とは、二つの異なる資産間の価格差を指します。暗号通貨のスワップを行う際、価格差は通常、交換する資産の価格と交換される資産の価格の違いを意味します。
価格差
@@ -1616,6 +1629,7 @@
で購入
受け取る
受け取る %s
+ 送信するのは%1$sトークンと%2$sネットワークのトークンのみ可能です、それ以外の場合、資金を失う可能性があります
送信
スワップ
資産
diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml
index 4142aa26be..17b029a572 100644
--- a/common/src/main/res/values-ko/strings.xml
+++ b/common/src/main/res/values-ko/strings.xml
@@ -173,6 +173,10 @@
%s에서 추가된 프록시 수의 한도에 도달했습니다. 새로운 프록시를 추가하려면 기존 프록시를 제거하세요.
최대 프록시 수에 도달했습니다
추가된 사용자 정의 네트워크는 여기에 나타납니다
+ 컬러
+ 외관
+ 토큰 아이콘
+ 화이트
입력된 계약 주소는 Nova에서 %s 토큰으로 이미 존재합니다.
입력된 계약 주소는 Nova에서 %s 토큰으로 존재합니다. 수정하시겠습니까?
이 토큰은 이미 존재합니다
@@ -185,6 +189,8 @@
계약 주소 입력
소수 자릿수 입력
심볼 입력
+ 네트워크
+ 토큰
토큰 추가
계약 주소
소수 자릿수
@@ -207,6 +213,7 @@
Ledger에서 이 토큰을 지원하지 않습니다.
네트워크 또는 토큰으로 검색
입력한 이름과 일치하는 네트워크 또는 토큰이 없습니다.
+ 토큰으로 검색
당신의 지갑
보낼 토큰이 없습니다.\n계정에 토큰을 구매하거나 수신하십시오.
지불할 토큰
@@ -235,6 +242,7 @@
백업
생체 인증
구매가 시작되었습니다! 최대 60분까지 기다려주세요. 이메일에서 상태를 추적할 수 있습니다.
+ %s 구매를 위한 네트워크 선택
구매를 계속하려면 Nova Wallet 앱에서 %s로 리디렉션됩니다.
브라우저에서 계속할까요?
자동 균형 노드
@@ -961,6 +969,8 @@
푸시 알림을 활성화함으로써 귀하는 우리의 %s 및 %s에 동의하게 됩니다
설정 탭에서 알림 설정에 접근하여 나중에 다시 시도해주세요
중요한 것을 놓치지 마세요!
+ %s 수신을 위한 네트워크 선택
+ 주소 복사
json을 붙여넣거나 파일을 업로드하세요...
파일 업로드
JSON 복원
@@ -1114,9 +1124,11 @@
사용할 수 없는 트랙
Ledger 장치를 찾기 위해 블루투스 스캔을 수행하려면 Nova는 위치 기능을 활성화해야 합니다.
기기 설정에서 지리적 위치를 활성화해주세요.
+ 네트워크 선택
트랙 선택
%d 중 %d
주소 또는 w3n
+ %s 전송을 위한 네트워크 선택
수신자가 시스템 계정입니다. 이 계정은 어떤 회사나 개인에 의해 제어되지 않습니다.\n여전히 이 전송을 수행하시겠습니까?
토큰이 소실됩니다
권한 부여
@@ -1524,6 +1536,7 @@
다른 금액 입력
%s로 네트워크 수수료를 지불하기 위해, Nova는 계정의 최소 %s 잔액을 유지하기 위해 자동으로 %s를 %s로 교환합니다.
블록체인이 모든 거래를 처리하고 검증하기 위해 부과하는 네트워크 수수료입니다. 네트워크 상태나 거래 속도에 따라 다를 수 있습니다.
+ %s 교환을 위한 네트워크 선택
풀에 교환할 수 있는 유동성이 충분하지 않습니다
가격 차이는 두 자산 간의 가격 차이를 나타냅니다. 암호화폐 교환 시, 가격 차이는 일반적으로 교환하는 자산의 가격과 교환받는 자산의 가격 간의 차이를 의미합니다.
가격 차이
@@ -1616,6 +1629,7 @@
구매 방법
받기
%s 수령
+ 이 주소로 %1$s 토큰과 %2$s 네트워크에서의 토큰만 보내세요, 그렇지 않으면 자금을 잃을 수 있습니다.
보내기
스왑
자산
diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml
index 641ea2cf6e..0721f8cab6 100644
--- a/common/src/main/res/values-pl/strings.xml
+++ b/common/src/main/res/values-pl/strings.xml
@@ -173,6 +173,10 @@
Osiągnąłeś limit %s dodanych proxy w %s. Usuń proxy, aby dodać nowe.
Osiągnięto maksymalną liczbę proxy
Dodane niestandardowe sieci\npojawią się tutaj
+ Kolorowe
+ Wygląd
+ ikony tokenów
+ Białe
Wprowadzony adres kontraktu jest obecny w Nova jako token %s.
Wprowadzony adres kontraktu jest obecny w Nova jako token %s. Czy na pewno chcesz go zmodyfikować?
Ten token już istnieje
@@ -185,6 +189,8 @@
Wprowadź adres kontraktu
Wprowadź liczbę miejsc dziesiętnych
Wprowadź symbol
+ Sieci
+ Tokeny
Dodaj token
Adres kontraktu
Liczba miejsc dziesiętnych
@@ -207,6 +213,7 @@
Ledger nie obsługuje tego tokena
Wyszukaj według sieci lub tokena
Nie znaleziono sieci ani tokenów z\npodaną nazwą
+ Wyszukaj według tokena
Twoje portfele
Nie masz tokenów do wysłania.\nKup lub odbierz tokeny na swoje\nkonto.
Token do zapłaty
@@ -235,6 +242,7 @@
Kopia zapasowa
Autoryzacja biometryczna
Zakup zainicjowany! Proszę czekać do 60 minut. Możesz śledzić status na e-mail.
+ Wybierz sieć do kupna %s
Aby kontynuować zakup, zostaniesz przekierowany z aplikacji Nova Wallet na %s
Kontynuować w przeglądarce?
Automatyczne równoważenie węzłów
@@ -982,6 +990,8 @@
Włączając powiadomienia push, zgadzasz się na nasze %s i %s
Spróbuj ponownie później, otwierając ustawienia powiadomień w zakładce Ustawienia
Nie przegap niczego!
+ Wybierz sieć do otrzymania %s
+ Skopiuj adres
Wklej json lub załaduj plik…
Załaduj plik
Przywróć JSON
@@ -1138,9 +1148,11 @@
Niedostępne trasy
Nova wymaga włączenia lokalizacji, aby móc przeprowadzać skanowanie Bluetooth w celu znalezienia urządzenia Ledger
Proszę włączyć geolokalizację w ustawieniach urządzenia
+ Wybierz sieć
Wybierz trasy dla
%d z %d
Adres lub w3n
+ Wybierz sieć do wysyłki %s
Odbiorca jest kontem systemowym. Nie jest kontrolowany przez żadną firmę ani osobę prywatną.\nCzy na pewno chcesz wykonać ten transfer?
Tokeny zostaną utracone
Nadaj uprawnienia
@@ -1554,6 +1566,7 @@
Wprowadź inną kwotę
Aby zapłacić opłatę sieciową za pomocą %s, Nova automatycznie zamieni %s na %s, aby utrzymać minimalne saldo %s na twoim koncie.
Opłaty sieciowe pobierane przez blockchain za przetwarzanie i walidację dowolnych transakcji. Mogą się różnić w zależności od warunków sieci lub szybkości transakcji.
+ Wybierz sieć do zamiany %s
W puli brakuje płynności do wymiany
Różnica cenowa odnosi się do różnicy w cenie pomiędzy dwoma różnymi aktywami. Podczas wymiany w kryptowalutach, różnica cenowa zazwyczaj oznacza różnicę między ceną aktywa, którą otrzymujesz, a ceną aktywa, którą płacisz.
Różnica cen
@@ -1646,6 +1659,7 @@
Kup za
Otrzymaj
Otrzymaj %s
+ Wysyłaj tylko token %1$s i tokeny w sieci %2$s na ten adres, w przeciwnym razie możesz stracić swoje środki
Wyślij
Wymiana
Aktywa
diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml
index f42fb5c89f..a6aab8484e 100644
--- a/common/src/main/res/values-pt/strings.xml
+++ b/common/src/main/res/values-pt/strings.xml
@@ -173,6 +173,10 @@
Você alcançou o limite de %s proxies adicionados em %s. Remova proxies para adicionar novos.
Número máximo de proxies foi alcançado
Redes personalizadas adicionadas\naparecerão aqui
+ Colorido
+ Aparência
+ ícones de token
+ Branco
O endereço do contrato inserido já está presente na Nova como um token %s.
O endereço do contrato inserido já está presente na Nova como um token %s. Tem certeza de que deseja modificá-lo?
Este token já existe
@@ -185,6 +189,8 @@
Insira o endereço do contrato
Insira os decimais
Insira o símbolo
+ Redes
+ Tokens
Adicionar token
Endereço do contrato
Decimais
@@ -207,6 +213,7 @@
Ledger não suporta este token
Buscar por rede ou token
Nenhuma rede ou token com o nome inserido foi encontrado
+ Pesquisar por token
Suas carteiras
Você não tem tokens para enviar. Compre ou Receba tokens na sua conta.
Token para pagamento
@@ -235,6 +242,7 @@
Backup
Autenticação biométrica
Compra iniciada! Por favor, aguarde até 60 minutos. Você pode rastrear o status pelo email.
+ Selecione a rede para comprar %s
Para continuar a compra você será redirecionado do aplicativo Nova Wallet para %s
Continuar no navegador?
Equilibrar nós automaticamente
@@ -968,6 +976,8 @@
Ao ativar as notificações push, você concorda com nossos %s e %s
Por favor, tente novamente mais tarde acessando as configurações de notificação na aba Configurações
Não perca nada!
+ Selecione a rede para receber %s
+ Copiar Endereço
Cole o json ou faça upload do arquivo…
Fazer upload de arquivo
Restaurar JSON
@@ -1122,9 +1132,11 @@
Faixas indisponíveis
Nova precisa que a localização seja ativada para poder realizar a varredura Bluetooth para encontrar seu dispositivo Ledger
Por favor, ative a localização geográfica nas configurações do dispositivo
+ Selecionar rede
Selecione faixas para
%d de %d
Endereço ou w3n
+ Selecione a rede para enviar %s
O destinatário é uma conta do sistema. Não é controlado por nenhuma empresa ou indivíduo.\nVocê ainda deseja realizar esta transferência?
Os tokens serão perdidos
Dar autoridade para
@@ -1534,6 +1546,7 @@
Digite outra quantidade
Para pagar a taxa de rede com %s, a Nova fará automaticamente a troca de %s por %s para manter o saldo mínimo de %s na sua conta.
Uma taxa de rede cobrada pela blockchain para processar e validar quaisquer transações. Pode variar dependendo das condições da rede ou velocidade da transação.
+ Selecione a rede para trocar %s
O pool não tem liquidez suficiente para a troca
Diferença de preço refere-se à diferença de preço entre dois ativos diferentes. Ao fazer uma troca em cripto, a diferença de preço geralmente é entre o preço do ativo que você está trocando e o preço do ativo com o qual você está trocando.
Diferença de preço
@@ -1626,6 +1639,7 @@
Comprar com
Receber
Receber %s
+ Envie somente o token %1$s e tokens na rede %2$s para este endereço, ou você pode perder seus fundos
Enviar
Trocar
Ativos
diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml
index cd1f87d762..33dc180a91 100644
--- a/common/src/main/res/values-ru/strings.xml
+++ b/common/src/main/res/values-ru/strings.xml
@@ -173,6 +173,10 @@
Вы достигли лимита добавленных прокси (%s) в %s . Удалите прокси, чтобы добавить новые.
Достигнуто максимальное количество прокси
Добавленные пользовательские сети\nпоявятся здесь
+ Цветной
+ Внешний вид
+ иконки токенов
+ Белый
Введенный адрес контракта уже добавлен в Nova как токен %s.
Введенный адрес контракта присутствует в Nova как токен %s. Вы уверены, что хотите изменить его?
Этот токен уже добавлен
@@ -185,6 +189,8 @@
Введите адрес контракта
Введите число десятичных знаков
Введите символ
+ Сети
+ Токены
Добавить токен
Адрес контракта
Число десятичных знаков
@@ -207,6 +213,7 @@
Ledger не поддерживает этот токен
Поиск по названию сети или токена
Токены и сети с указанным именем\nне найдены
+ Поиск по токену
Ваши кошельки
У вас нет токенов для отправки.\nКупите или получите токены\nна свой аккаунт.
Токен для оплаты
@@ -235,6 +242,7 @@
Бэкап
Биометрия
Покупка совершена! Ожидайте до 60 минут. Вы можете отслеживать статус по электронной почте.
+ Выберите сеть для покупки %s
Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %s
Продолжить в браузере?
Автоматическая балансировка нод
@@ -982,6 +990,8 @@
Включая push-уведомления, вы соглашаетесь с нашими %s и %s
Повторите попытку позже, открыв настройки уведомлений на вкладке «Настройки»
Не пропустите ничего!
+ Выберите сеть для получения %s
+ Скопировать адрес
Вставьте json строку или загрузите файл…
Загрузите файл
JSON для восстановления
@@ -1138,9 +1148,11 @@
Недоступные треки
Nova нуждается в включении местоположения, чтобы иметь возможность выполнять сканирование Bluetooth для поиска вашего устройства Ledger
Пожалуйста, включите геолокацию в настройках устройства
+ Выберите сеть
Выберите треки для
%d из %d
Адрес или w3n
+ Выберите сеть для отправки %s
Получатель является системным аккаунтом. Этот аккаунт не контролируется какой-либо компанией или частным лицом. \nВы уверены, что все еще хотите выполнить данный перевод?
Токены будут потеряны
Выдать полномочия аккаунту
@@ -1554,6 +1566,7 @@
Введите другую сумму
Чтобы оплатить комиссию сети с помощью %s, Nova автоматически обменяет %s на %s, чтобы поддерживать минимальный %s баланс вашей учетной записи.
Комиссия сети, взимается блокчейном за обработку и проверку транзакций. Может варьироваться в зависимости от условий сети или скорости транзакции.
+ Выберите сеть для обмена %s
В пуле недостаточно ликвидности для обмена
Разница в цене представляет собой разницу между двумя различными активами. При обмене криптовалюты под разницей в цене обычно имеется ввиду разница между ценой актива, которую вы получаете и ценой актива, которую вы платите.
Разница в цене
@@ -1646,6 +1659,7 @@
Купить с
Получить
Получить %s
+ Отправляйте только токен %1$s и токены в сети %2$s на этот адрес, иначе вы можете потерять свои средства
Перевести
Обменять
Активы
diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml
index aa73d206fc..1775904096 100644
--- a/common/src/main/res/values-tr/strings.xml
+++ b/common/src/main/res/values-tr/strings.xml
@@ -173,6 +173,10 @@
%s\'da eklenen maksimum proxy (%s) limitine ulaştınız. Yeni eklemeler yapabilmek için proxy\'leri kaldırın.
Maksimum proxy sayısına ulaşıldı
Eklenen özel ağlar\nburada görünecek
+ Renkli
+ Görünüm
+ token simgeleri
+ Beyaz
Girilen sözleşme adresi Nova\'da zaten %s tokeni olarak bulunmaktadır.
Girilen sözleşme adresi Nova\'da zaten %s tokeni olarak bulunmaktadır. Değiştirmek istediğinize emin misiniz?
Bu token zaten var
@@ -185,6 +189,8 @@
Sözleşme adresi girin
Ondalık sayısını girin
Sembol girin
+ Ağlar
+ Tokenler
Token ekle
Sözleşme adresi
Ondalıklar
@@ -207,6 +213,7 @@
Ledger bu token\'i desteklemiyor
Ağ veya token adına göre ara
Girilen adla herhangi bir ağ veya token bulunamadı
+ Tokene göre ara
Cüzdanlarınız
Göndermek için token\'ınız yok.\nHesabınıza token alın veya alın.
Ödeme yapılacak Token
@@ -235,6 +242,7 @@
Yedekleme
Biyometrik Doğrulama
Satın alma başlatıldı! Lütfen 60 dakika kadar bekleyin. Durumu e-postadan takip edebilirsiniz.
+ Satın alma için ağ seçin %s
Satın alma işlemine devam etmek için Nova Wallet uygulamasından %s\'a yönlendirileceksiniz
Tarayıcıda devam et?
Otomatik denge düğümleri
@@ -968,6 +976,8 @@
Bildirimleri etkinleştirmekle, %s ve %s kabul etmiş olursunuz
Lütfen, Ayarlar sekmesinden bildirim ayarlarına erişerek daha sonra tekrar deneyin
Hiçbir şeyi kaçırmayın!
+ Alma için ağ seçin %s
+ Adresi Kopyala
Json yapıştırın ya da dosya yükleyin...
Dosya yükle
JSON Kurtar
@@ -1122,9 +1132,11 @@
Ulaşılamayan yollar
Nova\'nın Bluetooth taraması yaparak Ledger cihazınızı bulabilmesi için konumun etkinleştirilmesi gerekiyor
Lütfen cihaz ayarlarında coğrafi konumu etkinleştirin
+ Ağ seç
İçin şarkıları seçin
%d / %d
Adres veya w3n
+ Gönderme için ağ seçin %s
Alıcı, bir sistem hesabıdır. Bir şirket veya birey tarafından kontrol edilmez.\nBu transferi gerçekleştirmek istediğinize emin misiniz?
Token\'lar kaybolacak
Yetki ver
@@ -1534,6 +1546,7 @@
Başka bir miktar girin
%s ile ağ ücreti ödemek için, Nova otomatik olarak hesabınızın minimum %s bakiyesini korumak için %s\'i %s\'ye dönüştürecektir.
Herhangi bir işlemi işlemek ve doğrulamak için blockchain tarafından alınan ağ ücretleri. Ağ koşullarına veya işlem hızına bağlı olarak değişebilir.
+ Takas için ağ seçin %s
Havuzda takas için yeterli likidite yok
Fiyat farkı, iki farklı varlık arasındaki fiyat farkını ifade eder. Kripto takas yaparken, fiyat farkı genellikle takas ettiğiniz varlığın fiyatı ile takas ettiğiniz varlığın fiyatı arasındaki farktır.
Fiyat farkı
@@ -1626,6 +1639,7 @@
ile Satın al
Al
%s al
+ Bu adrese yalnızca %1$s token ve %2$s ağındaki tokenleri gönderin, aksi takdirde paranızı kaybedebilirsiniz
Gönder
Takas et
Varlıklar
diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml
index 6ebc3b7831..7379a43bef 100644
--- a/common/src/main/res/values-vi/strings.xml
+++ b/common/src/main/res/values-vi/strings.xml
@@ -173,6 +173,10 @@
Bạn đã đạt đến giới hạn %s proxy được thêm vào %s. Xóa proxy để thêm proxy mới.
Đã đạt đến số lượng proxy tối đa
Các mạng tùy chỉnh đã thêm\nsẽ xuất hiện ở đây
+ Đã tô màu
+ Giao diện
+ biểu tượng token
+ Trắng
Địa chỉ hợp đồng đã nhập có sẵn trong Nova là token %s.
Địa chỉ hợp đồng đã nhập có sẵn trong Nova là token %s. Bạn có chắc chắn muốn thay đổi không?
Token này đã tồn tại
@@ -185,6 +189,8 @@
Nhập địa chỉ hợp đồng
Nhập số thập phân
Nhập ký hiệu
+ Mạng
+ Token
Thêm token
Địa chỉ hợp đồng
Số thập phân
@@ -207,6 +213,7 @@
Ledger không hỗ trợ token này
Tìm kiếm theo mạng hoặc token
Không tìm thấy mạng hoặc token nào với\ntên đã nhập
+ Tìm kiếm theo token
Ví của bạn
Bạn không có token để gửi.\nMua hoặc Nhận token vào\ntài khoản của bạn.
Token để thanh toán
@@ -235,6 +242,7 @@
Sao lưu
Xác thực sinh trắc học
Mua hàng đã được khởi tạo! Vui lòng đợi tối đa 60 phút. Bạn có thể theo dõi trạng thái trong email.
+ Chọn mạng để mua %s
Để tiếp tục mua hàng, bạn sẽ được chuyển hướng từ ứng dụng Nova Wallet tới %s
Tiếp tục trong trình duyệt?
Tự động cân bằng nodes
@@ -961,6 +969,8 @@
Bằng việc bật thông báo đẩy, bạn đồng ý với %s và %s của chúng tôi
Vui lòng thử lại sau bằng cách truy cập cài đặt thông báo từ tab Cài đặt
Đừng bỏ lỡ điều gì!
+ Chọn mạng để nhận %s
+ Sao chép Địa chỉ
Dán mã JSON hoặc tải lên tệp...
Tải lên tệp
Khôi phục JSON
@@ -1114,9 +1124,11 @@
Track không có sẵn
Nova cần bật định vị để có thể thực hiện quét Bluetooth để tìm thiết bị Ledger của bạn
Vui lòng bật định vị địa lý trong cài đặt thiết bị
+ Chọn mạng
Chọn track cho
%d trong %d
Địa chỉ hoặc w3n
+ Chọn mạng để gửi %s
Người nhận là một tài khoản hệ thống. Nó không được kiểm soát bởi bất kỳ công ty hoặc cá nhân nào.\nBạn có chắc chắn vẫn muốn thực hiện chuyển khoản này không?
Token sẽ bị mất
Cấp quyền cho
@@ -1524,6 +1536,7 @@
Nhập số tiền khác
Để thanh toán phí mạng bằng %s, Nova sẽ tự động hoán đổi %s thành %s để duy trì số dư tối thiểu %s trong tài khoản của bạn.
Một khoản phí mạng được tính bởi blockchain để xử lý và xác nhận bất kỳ giao dịch nào. Có thể thay đổi tùy thuộc vào điều kiện mạng hoặc tốc độ giao dịch.
+ Chọn mạng để hoán đổi %s
Pool không có đủ thanh khoản để thực hiện hoán đổi
Sự khác biệt về giá đề cập đến sự chênh lệch giá giữa hai tài sản khác nhau. Khi thực hiện hoán đổi trong tiền điện tử, sự khác biệt về giá thường là sự chênh lệch giữa giá của tài sản mà bạn đang hoán đổi và giá của tài sản mà bạn đang hoán đổi.
Sự khác biệt về giá
@@ -1616,6 +1629,7 @@
Mua với
Nhận
Nhận %s
+ Chỉ gửi token %1$s và các token trong mạng %2$s tới địa chỉ này, nếu không bạn có thể mất tiền
Gửi
Hoán đổi
Tài sản
diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml
index cc1c960d19..8e720b42a4 100644
--- a/common/src/main/res/values-zh-rCN/strings.xml
+++ b/common/src/main/res/values-zh-rCN/strings.xml
@@ -173,6 +173,10 @@
您已达到在%s中添加的代理的上限%s。移除代理以添加新代理。
已达到代理最大数量
添加的自定义网络会显示在这里
+ 彩色图标
+ 外观
+ 代币图标
+ 白色图标
输入的合约地址已作为%s代币存在于Nova中。
输入的合约地址已作为%s代币存在于Nova中。您确定要修改它吗?
该代币已存在
@@ -185,6 +189,8 @@
输入合约地址
输入小数
输入符号
+ 网络
+ 代币
添加代币
合约地址
小数点
@@ -207,6 +213,7 @@
Ledger 不支持此代币
按网络或代币搜索
没有找到输入名称的网络或代币
+ 按代币搜索
你的钱包
你没有要发送的代币。购买或接收代币到你的账户。
要支付的代币
@@ -235,6 +242,7 @@
备份
生物认证
购买已启动!请等待最多 60 分钟。你可以在电子邮件上追踪状态。
+ 选择购买%s的网络
为了继续购买,你将从 Nova Wallet 应用被重定向到 %s
在浏览器中继续?
自动平衡节点
@@ -961,6 +969,8 @@
启用推送通知,即表示您同意我们的 %s 和 %s
请稍后再试,通过设置选项卡中的通知设置访问
不要错过任何事情!
+ 选择接收%s的网络
+ 复制地址
粘贴 json 字符串或上传文件…
上传文件
恢复 JSON
@@ -1114,9 +1124,11 @@
不可用的轨道
Nova需要启用地理位置,以便能够进行蓝牙扫描查找您的Ledger设备
请在设备设置中启用地理位置
+ 选择网络
选择曲目
%d / %d
地址或w3n
+ 选择发送%s的网络
收件人是一个系统账户。它不受任何公司或个人控制。您确定仍然要执行此转账吗?
代币将会丢失
授权给
@@ -1524,6 +1536,7 @@
输入其他金额
为了支付网络费用,Nova将自动将%s兑换为%s,以保持您账户的最低%s余额。
区块链收取的网络费用,用于处理和验证任何交易。可能会根据网络状况或交易速度而变化。
+ 选择交换%s的网络
池子没有足够的流动性进行交换
价格差异指的是两种不同资产之间的价格差。在加密货币交换中,价格差异通常是您要交换的资产的价格与您交换的资产的价格之间的差异。
价格差异
@@ -1616,6 +1629,7 @@
用...购买
接收
接收 %s
+ 仅将%1$s代币和%2$s网络中的代币发送到此地址,否则您可能会失去资金
发送
兑换
资产
diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml
index 65457f8835..409cd3792e 100644
--- a/common/src/main/res/values/colors.xml
+++ b/common/src/main/res/values/colors.xml
@@ -3,6 +3,7 @@
#eeeeee
#FFFFFF
+ #101014
#05081C
@@ -13,8 +14,10 @@
#E0FFFFFF
- #05081C
+ #000000
#7AFFFFFF
+ #8F000000
+ #05081C
#E0FFFFFF
#A3FFFFFF
#52FFFFFF
@@ -61,6 +64,7 @@
#08090E
#181920
#1A999EC7
+ #3D999EC7
#5205081C
#291F78FF
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 862440d046..b157bffc3d 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -1,6 +1,26 @@
+ Select network
+
+ Search by token
+
+ Copy Address
+ Send only %1$s token and tokens in %2$s network to this address, or you might lose your funds
+
+ token icons
+ Appearance
+ White
+ Colored
+
+ Select network for buying %s
+ Select network for receiving %s
+ Select network for sending %s
+ Select network for swaping %s
+
+ Tokens
+ Networks
+
Wiki & Help Center
Get support via Email
diff --git a/common/src/main/res/values/styles.xml b/common/src/main/res/values/styles.xml
index c9f36fd1b8..63d87457d5 100644
--- a/common/src/main/res/values/styles.xml
+++ b/common/src/main/res/values/styles.xml
@@ -457,10 +457,14 @@
- @color/selector_button_background_secondary
-
+
+
diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt
index a5f61e4aa0..dc883f4cc6 100644
--- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt
+++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt
@@ -6,8 +6,13 @@ import android.widget.ImageView
import coil.ImageLoader
import coil.load
import coil.request.ImageRequest
+import io.novafoundation.nova.common.data.model.AssetIconMode
+import io.novafoundation.nova.common.presentation.AssetIconProvider
+import io.novafoundation.nova.common.presentation.fallbackIcon
+import io.novafoundation.nova.common.presentation.getAssetIconOrFallback
import io.novafoundation.nova.common.utils.images.Icon
import io.novafoundation.nova.common.utils.images.asIcon
+import io.novafoundation.nova.common.utils.images.setIcon
import io.novafoundation.nova.feature_account_api.R
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
@@ -39,16 +44,12 @@ fun ImageLoader.loadChainIconToTarget(icon: String?, context: Context, target: (
this.enqueue(request)
}
-fun ImageView.loadTokenIcon(icon: String?, imageLoader: ImageLoader) {
- load(icon, imageLoader) {
+fun ImageView.setTokenIcon(icon: Icon, imageLoader: ImageLoader) {
+ setIcon(icon, imageLoader) {
fallback(ASSET_ICON_PLACEHOLDER)
}
}
-fun Chain.Asset.icon(): Icon {
- return iconUrl?.asIcon() ?: ASSET_ICON_PLACEHOLDER.asIcon()
-}
-
fun Chain.iconOrFallback(): Icon {
return icon?.asIcon() ?: chainIconFallback()
}
@@ -60,3 +61,11 @@ fun String?.asIconOrFallback(): Icon {
fun chainIconFallback(): Icon {
return R.drawable.ic_fallback_network_icon.asIcon()
}
+
+fun AssetIconProvider.getAssetIconOrFallback(asset: Chain.Asset, fallbackIcon: Icon = AssetIconProvider.fallbackIcon): Icon {
+ return this.getAssetIconOrFallback(asset.icon, fallbackIcon)
+}
+
+fun AssetIconProvider.getAssetIconOrFallback(asset: Chain.Asset, iconMode: AssetIconMode, fallbackIcon: Icon = AssetIconProvider.fallbackIcon): Icon {
+ return this.getAssetIconOrFallback(asset.icon, iconMode, fallbackIcon)
+}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
index a401b17e45..059d7d2f94 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
@@ -9,17 +9,20 @@ import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_assets.presentation.balance.detail.di.BalanceDetailComponent
-import io.novafoundation.nova.feature_assets.presentation.balance.filters.di.AssetFiltersComponent
import io.novafoundation.nova.feature_assets.presentation.balance.list.di.BalanceListComponent
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.GoToNftsView
import io.novafoundation.nova.feature_assets.presentation.balance.search.di.AssetSearchComponent
-import io.novafoundation.nova.feature_assets.presentation.buy.flow.di.AssetBuyFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.buy.flow.asset.di.AssetBuyFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.buy.flow.network.di.NetworkBuyFlowComponent
import io.novafoundation.nova.feature_assets.presentation.receive.di.ReceiveComponent
-import io.novafoundation.nova.feature_assets.presentation.receive.flow.di.AssetReceiveFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.receive.flow.asset.di.AssetReceiveFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.receive.flow.network.di.NetworkReceiveFlowComponent
import io.novafoundation.nova.feature_assets.presentation.send.amount.di.SelectSendComponent
import io.novafoundation.nova.feature_assets.presentation.send.confirm.di.ConfirmSendComponent
-import io.novafoundation.nova.feature_assets.presentation.send.flow.di.AssetSendFlowComponent
-import io.novafoundation.nova.feature_assets.presentation.swap.di.AssetSwapFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.send.flow.asset.di.AssetSendFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.send.flow.network.di.NetworkSendFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.swap.asset.di.AssetSwapFlowComponent
+import io.novafoundation.nova.feature_assets.presentation.swap.network.di.NetworkSwapFlowComponent
import io.novafoundation.nova.feature_assets.presentation.tokens.add.enterInfo.di.AddTokenEnterInfoComponent
import io.novafoundation.nova.feature_assets.presentation.tokens.add.selectChain.di.AddTokenSelectChainComponent
import io.novafoundation.nova.feature_assets.presentation.tokens.manage.chain.di.ManageChainTokensComponent
@@ -74,8 +77,6 @@ interface AssetsFeatureComponent : AssetsFeatureApi {
fun receiveComponentFactory(): ReceiveComponent.Factory
- fun assetFiltersComponentFactory(): AssetFiltersComponent.Factory
-
fun assetSearchComponentFactory(): AssetSearchComponent.Factory
fun manageTokensComponentFactory(): ManageTokensComponent.Factory
@@ -94,6 +95,14 @@ interface AssetsFeatureComponent : AssetsFeatureApi {
fun buyFlowComponent(): AssetBuyFlowComponent.Factory
+ fun networkBuyFlowComponent(): NetworkBuyFlowComponent.Factory
+
+ fun networkReceiveFlowComponent(): NetworkReceiveFlowComponent.Factory
+
+ fun networkSendFlowComponent(): NetworkSendFlowComponent.Factory
+
+ fun networkSwapFlowComponent(): NetworkSwapFlowComponent.Factory
+
fun inject(view: GoToNftsView)
@Component.Factory
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt
index 1ce1f7e787..86d943ecbe 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt
@@ -10,12 +10,16 @@ import io.novafoundation.nova.common.data.network.AppLinksProvider
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.data.network.coingecko.CoinGeckoLinkParser
+import io.novafoundation.nova.common.data.repository.AssetsIconModeRepository
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences
+import io.novafoundation.nova.common.domain.interactor.AssetViewModeInteractor
import io.novafoundation.nova.common.interfaces.FileProvider
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory
+import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.ClipboardManager
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.QrCodeGenerator
@@ -87,6 +91,88 @@ import javax.inject.Named
interface AssetsFeatureDependencies {
+ val assetsSourceRegistry: AssetSourceRegistry
+
+ val addressInputMixinFactory: AddressInputMixinFactory
+
+ val multiChainQrSharingFactory: MultiChainQrSharingFactory
+
+ val walletUiUseCase: WalletUiUseCase
+
+ val computationalCache: ComputationalCache
+
+ val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
+
+ val crossChainTraRepository: CrossChainTransfersRepository
+
+ val crossChainWeigher: CrossChainWeigher
+
+ val crossChainTransactor: CrossChainTransactor
+
+ val resourcesHintsMixinFactory: ResourcesHintsMixinFactory
+
+ val parachainInfoRepository: ParachainInfoRepository
+
+ val watchOnlyMissingKeysPresenter: WatchOnlyMissingKeysPresenter
+
+ val balanceLocksRepository: BalanceLocksRepository
+
+ val chainAssetRepository: ChainAssetRepository
+
+ val erc20Standard: Erc20Standard
+
+ val externalBalanceRepository: ExternalBalanceRepository
+
+ val pooledBalanceUpdaterFactory: PooledBalanceUpdaterFactory
+
+ val paymentUpdaterFactory: PaymentUpdaterFactory
+
+ val locksUpdaterFactory: BalanceLocksUpdaterFactory
+
+ val accountUpdateScope: AccountUpdateScope
+
+ val storageSharedRequestBuilderFactory: StorageSharedRequestsBuilderFactory
+
+ val poolDisplayUseCase: PoolDisplayUseCase
+
+ val poolAccountDerivation: PoolAccountDerivation
+
+ val operationDao: OperationDao
+
+ val coinPriceRepository: CoinPriceRepository
+
+ val swapSettingsStateProvider: SwapSettingsStateProvider
+
+ val swapService: SwapService
+
+ val swapAvailabilityInteractor: SwapAvailabilityInteractor
+
+ val bannerVisibilityRepository: BannerVisibilityRepository
+
+ val buyMixinFactory: BuyMixin.Factory
+
+ val buyMixinUi: BuyMixinUi
+
+ val crossChainTransfersUseCase: CrossChainTransfersUseCase
+
+ val arbitraryTokenUseCase: ArbitraryTokenUseCase
+
+ val swapRateFormatter: SwapRateFormatter
+
+ val bottomSheetLauncher: DescriptionBottomSheetLauncher
+
+ val selectAddressMixinFactory: SelectAddressMixin.Factory
+
+ val chainStateRepository: ChainStateRepository
+
+ val holdsRepository: BalanceHoldsRepository
+
+ val holdsDao: HoldsDao
+
+ val coinGeckoLinkParser: CoinGeckoLinkParser
+
+ val assetIconProvider: AssetIconProvider
+
fun web3NamesInteractor(): Web3NamesInteractor
fun contributionsInteractor(): ContributionsInteractor
@@ -169,85 +255,13 @@ interface AssetsFeatureDependencies {
fun coingeckoApi(): CoingeckoApi
+ fun assetsViewModeRepository(): AssetsViewModeRepository
+
fun walletConnectSessionsUseCase(): WalletConnectSessionsUseCase
- val assetsSourceRegistry: AssetSourceRegistry
+ fun assetsIconModeRepository(): AssetsIconModeRepository
fun nftRepository(): NftRepository
- val addressInputMixinFactory: AddressInputMixinFactory
-
- val multiChainQrSharingFactory: MultiChainQrSharingFactory
-
- val walletUiUseCase: WalletUiUseCase
-
- val computationalCache: ComputationalCache
-
- val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
-
- val crossChainTraRepository: CrossChainTransfersRepository
- val crossChainWeigher: CrossChainWeigher
- val crossChainTransactor: CrossChainTransactor
-
- val resourcesHintsMixinFactory: ResourcesHintsMixinFactory
-
- val parachainInfoRepository: ParachainInfoRepository
-
- val watchOnlyMissingKeysPresenter: WatchOnlyMissingKeysPresenter
-
- val balanceLocksRepository: BalanceLocksRepository
-
- val chainAssetRepository: ChainAssetRepository
-
- val erc20Standard: Erc20Standard
-
- val externalBalanceRepository: ExternalBalanceRepository
-
- val pooledBalanceUpdaterFactory: PooledBalanceUpdaterFactory
-
- val paymentUpdaterFactory: PaymentUpdaterFactory
-
- val locksUpdaterFactory: BalanceLocksUpdaterFactory
-
- val accountUpdateScope: AccountUpdateScope
-
- val storageSharedRequestBuilderFactory: StorageSharedRequestsBuilderFactory
-
- val poolDisplayUseCase: PoolDisplayUseCase
-
- val poolAccountDerivation: PoolAccountDerivation
-
- val operationDao: OperationDao
-
- val coinPriceRepository: CoinPriceRepository
-
- val swapSettingsStateProvider: SwapSettingsStateProvider
-
- val swapService: SwapService
-
- val swapAvailabilityInteractor: SwapAvailabilityInteractor
-
- val bannerVisibilityRepository: BannerVisibilityRepository
-
- val buyMixinFactory: BuyMixin.Factory
-
- val buyMixinUi: BuyMixinUi
-
- val crossChainTransfersUseCase: CrossChainTransfersUseCase
-
- val arbitraryTokenUseCase: ArbitraryTokenUseCase
-
- val swapRateFormatter: SwapRateFormatter
-
- val bottomSheetLauncher: DescriptionBottomSheetLauncher
-
- val selectAddressMixinFactory: SelectAddressMixin.Factory
-
- val chainStateRepository: ChainStateRepository
-
- val holdsRepository: BalanceHoldsRepository
-
- val holdsDao: HoldsDao
-
- val coinGeckoLinkParser: CoinGeckoLinkParser
+ fun assetViewModeInteractor(): AssetViewModeInteractor
}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureModule.kt
index 3c99128536..1e7a4e6eb6 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureModule.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureModule.kt
@@ -3,9 +3,11 @@ package io.novafoundation.nova.feature_assets.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.memory.ComputationalCache
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
+import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.core_db.dao.OperationDao
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
@@ -23,20 +25,31 @@ import io.novafoundation.nova.feature_assets.domain.WalletInteractor
import io.novafoundation.nova.feature_assets.domain.WalletInteractorImpl
import io.novafoundation.nova.feature_assets.domain.assets.ExternalBalancesInteractor
import io.novafoundation.nova.feature_assets.domain.assets.RealExternalBalancesInteractor
-import io.novafoundation.nova.feature_assets.domain.assets.search.AssetSearchInteractor
+import io.novafoundation.nova.feature_assets.domain.assets.search.AssetSearchInteractorFactory
+import io.novafoundation.nova.feature_assets.domain.assets.search.AssetSearchUseCase
+import io.novafoundation.nova.feature_assets.domain.assets.search.AssetViewModeAssetSearchInteractorFactory
+import io.novafoundation.nova.feature_assets.domain.networks.AssetNetworksInteractor
+import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_assets.presentation.balance.common.ControllableAssetCheckMixin
+import io.novafoundation.nova.feature_assets.presentation.balance.common.ExpandableAssetsMixinFactory
+import io.novafoundation.nova.feature_assets.presentation.swap.executor.InitialSwapFlowExecutor
+import io.novafoundation.nova.feature_assets.presentation.swap.executor.SwapFlowExecutorFactory
import io.novafoundation.nova.feature_assets.presentation.transaction.filter.HistoryFiltersProviderFactory
+import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor
import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository
import io.novafoundation.nova.feature_nft_api.data.repository.NftRepository
import io.novafoundation.nova.feature_staking_api.data.network.blockhain.updaters.PooledBalanceUpdaterFactory
import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation
import io.novafoundation.nova.feature_swap_api.domain.swap.SwapService
+import io.novafoundation.nova.feature_swap_api.presentation.state.SwapSettingsStateProvider
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.updaters.BalanceLocksUpdaterFactory
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.updaters.PaymentUpdaterFactory
import io.novafoundation.nova.feature_wallet_api.data.repository.ExternalBalanceRepository
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.CoinPriceRepository
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
+import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountFormatter
+import io.novafoundation.nova.feature_wallet_api.presentation.model.RealAmountFormatter
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
@@ -52,13 +65,28 @@ class AssetsFeatureModule {
@Provides
@FeatureScope
- fun provideSearchInteractor(
+ fun provideAssetSearchUseCase(
walletRepository: WalletRepository,
accountRepository: AccountRepository,
chainRegistry: ChainRegistry,
- assetSourceRegistry: AssetSourceRegistry,
swapService: SwapService
- ) = AssetSearchInteractor(walletRepository, accountRepository, chainRegistry, assetSourceRegistry, swapService)
+ ) = AssetSearchUseCase(walletRepository, accountRepository, chainRegistry, swapService)
+
+ @Provides
+ @FeatureScope
+ fun provideSearchInteractorFactory(
+ assetViewModeRepository: AssetsViewModeRepository,
+ assetSearchUseCase: AssetSearchUseCase,
+ chainRegistry: ChainRegistry
+ ): AssetSearchInteractorFactory = AssetViewModeAssetSearchInteractorFactory(assetViewModeRepository, assetSearchUseCase, chainRegistry)
+
+ @Provides
+ @FeatureScope
+ fun provideAssetNetworksInteractor(
+ chainRegistry: ChainRegistry,
+ swapService: SwapService,
+ assetSearchUseCase: AssetSearchUseCase
+ ) = AssetNetworksInteractor(chainRegistry, swapService, assetSearchUseCase)
@Provides
@FeatureScope
@@ -141,4 +169,44 @@ class AssetsFeatureModule {
coinPriceRepository = coinPriceRepository,
poolAccountDerivation = poolAccountDerivation
)
+
+ @Provides
+ @FeatureScope
+ fun provideInitialSwapFlowExecutor(
+ assetsRouter: AssetsRouter
+ ): InitialSwapFlowExecutor {
+ return InitialSwapFlowExecutor(assetsRouter)
+ }
+
+ @Provides
+ @FeatureScope
+ fun provideSwapExecutor(
+ initialSwapFlowExecutor: InitialSwapFlowExecutor,
+ assetsRouter: AssetsRouter,
+ swapSettingsStateProvider: SwapSettingsStateProvider
+ ): SwapFlowExecutorFactory {
+ return SwapFlowExecutorFactory(initialSwapFlowExecutor, assetsRouter, swapSettingsStateProvider)
+ }
+
+ @Provides
+ @FeatureScope
+ fun provideAmountFormatter(resourceManager: ResourceManager): AmountFormatter {
+ return RealAmountFormatter(resourceManager)
+ }
+
+ @Provides
+ @FeatureScope
+ fun provideExpandableAssetsMixinFactory(
+ assetIconProvider: AssetIconProvider,
+ currencyInteractor: CurrencyInteractor,
+ assetsViewModeRepository: AssetsViewModeRepository,
+ amountFormatter: AmountFormatter
+ ): ExpandableAssetsMixinFactory {
+ return ExpandableAssetsMixinFactory(
+ assetIconProvider,
+ currencyInteractor,
+ assetsViewModeRepository,
+ amountFormatter
+ )
+ }
}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/modules/ManageTokensCommonModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/modules/ManageTokensCommonModule.kt
index 00bfa90407..d4244715e4 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/modules/ManageTokensCommonModule.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/modules/ManageTokensCommonModule.kt
@@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_assets.di.modules
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
+import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_assets.domain.tokens.AssetsDataCleaner
import io.novafoundation.nova.feature_assets.domain.tokens.RealAssetsDataCleaner
@@ -21,8 +22,9 @@ class ManageTokensCommonModule {
@Provides
@FeatureScope
fun provideMultiChainTokenUiMapper(
+ assetIconProvider: AssetIconProvider,
resourceManager: ResourceManager
- ) = MultiChainTokenMapper(resourceManager)
+ ) = MultiChainTokenMapper(assetIconProvider, resourceManager)
@Provides
@FeatureScope
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractor.kt
index 3cf88388af..a4f6aa08c6 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractor.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractor.kt
@@ -3,8 +3,10 @@ package io.novafoundation.nova.feature_assets.domain
import io.novafoundation.nova.common.data.model.DataPage
import io.novafoundation.nova.common.data.model.PageOffset
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
-import io.novafoundation.nova.feature_assets.domain.common.AssetGroup
+import io.novafoundation.nova.feature_assets.domain.common.AssetWithNetwork
+import io.novafoundation.nova.feature_assets.domain.common.NetworkAssetGroup
import io.novafoundation.nova.feature_assets.domain.common.AssetWithOffChainBalance
+import io.novafoundation.nova.feature_assets.domain.common.TokenAssetGroup
import io.novafoundation.nova.feature_currency_api.domain.model.Currency
import io.novafoundation.nova.feature_nft_api.data.repository.NftSyncTrigger
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TransactionFilter
@@ -57,8 +59,13 @@ interface WalletInteractor {
filters: Set
): Result>
- suspend fun groupAssets(
+ suspend fun groupAssetsByNetwork(
assets: List,
externalBalances: List
- ): Map>
+ ): Map>
+
+ suspend fun groupAssetsByToken(
+ assets: List,
+ externalBalances: List
+ ): Map>
}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractorImpl.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractorImpl.kt
index 2113a506ce..58c03b6064 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractorImpl.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/WalletInteractorImpl.kt
@@ -8,9 +8,12 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn
import io.novafoundation.nova.feature_assets.data.repository.TransactionHistoryRepository
import io.novafoundation.nova.feature_assets.data.repository.assetFilters.AssetFiltersRepository
-import io.novafoundation.nova.feature_assets.domain.common.AssetGroup
+import io.novafoundation.nova.feature_assets.domain.common.AssetWithNetwork
+import io.novafoundation.nova.feature_assets.domain.common.NetworkAssetGroup
import io.novafoundation.nova.feature_assets.domain.common.AssetWithOffChainBalance
+import io.novafoundation.nova.feature_assets.domain.common.TokenAssetGroup
import io.novafoundation.nova.feature_assets.domain.common.groupAndSortAssetsByNetwork
+import io.novafoundation.nova.feature_assets.domain.common.groupAndSortAssetsByToken
import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository
import io.novafoundation.nova.feature_currency_api.domain.model.Currency
import io.novafoundation.nova.feature_nft_api.data.repository.NftRepository
@@ -168,12 +171,21 @@ class WalletInteractorImpl(
}
}
- override suspend fun groupAssets(
+ override suspend fun groupAssetsByNetwork(
assets: List,
externalBalances: List
- ): Map> {
+ ): Map> {
val chains = chainRegistry.enabledChainByIdFlow().first()
return groupAndSortAssetsByNetwork(assets, externalBalances.aggregatedBalanceByAsset(), chains)
}
+
+ override suspend fun groupAssetsByToken(
+ assets: List,
+ externalBalances: List
+ ): Map> {
+ val chains = chainRegistry.enabledChainByIdFlow().first()
+
+ return groupAndSortAssetsByToken(assets, externalBalances.aggregatedBalanceByAsset(), chains)
+ }
}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/list/AssetsListInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/list/AssetsListInteractor.kt
index f48ca7d1fb..13388f4e1a 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/list/AssetsListInteractor.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/list/AssetsListInteractor.kt
@@ -1,5 +1,7 @@
package io.novafoundation.nova.feature_assets.domain.assets.list
+import io.novafoundation.nova.common.data.model.AssetViewMode
+import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_nft_api.data.model.Nft
@@ -15,9 +17,16 @@ private const val BANNER_TAG = "CROWDLOAN_UNLOCK_BANNER"
class AssetsListInteractor(
private val accountRepository: AccountRepository,
private val nftRepository: NftRepository,
- private val bannerVisibilityRepository: BannerVisibilityRepository
+ private val bannerVisibilityRepository: BannerVisibilityRepository,
+ private val assetsViewModeRepository: AssetsViewModeRepository
) {
+ fun assetsViewModeFlow() = assetsViewModeRepository.assetsViewModeFlow()
+
+ suspend fun setAssetViewMode(assetViewModel: AssetViewMode) {
+ assetsViewModeRepository.setAssetsViewMode(assetViewModel)
+ }
+
suspend fun fullSyncNft(nft: Nft) = nftRepository.fullNftSync(nft)
fun observeNftPreviews(): Flow {
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/models/AssetsByViewModeResult.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/models/AssetsByViewModeResult.kt
new file mode 100644
index 0000000000..a51611a4a8
--- /dev/null
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/models/AssetsByViewModeResult.kt
@@ -0,0 +1,29 @@
+package io.novafoundation.nova.feature_assets.domain.assets.models
+
+import io.novafoundation.nova.common.utils.MultiMapList
+import io.novafoundation.nova.feature_assets.domain.common.AssetWithNetwork
+import io.novafoundation.nova.feature_assets.domain.common.AssetWithOffChainBalance
+import io.novafoundation.nova.feature_assets.domain.common.NetworkAssetGroup
+import io.novafoundation.nova.feature_assets.domain.common.TokenAssetGroup
+
+sealed interface AssetsByViewModeResult {
+
+ class ByNetworks(val assets: MultiMapList) : AssetsByViewModeResult
+
+ class ByTokens(val tokens: MultiMapList) : AssetsByViewModeResult
+}
+
+fun AssetsByViewModeResult.groupList(): List {
+ return when (this) {
+ is AssetsByViewModeResult.ByNetworks -> assets.keys.toList()
+ is AssetsByViewModeResult.ByTokens -> tokens.keys.toList()
+ }
+}
+
+fun MultiMapList.byNetworks(): AssetsByViewModeResult {
+ return AssetsByViewModeResult.ByNetworks(this)
+}
+
+fun MultiMapList.byTokens(): AssetsByViewModeResult {
+ return AssetsByViewModeResult.ByTokens(this)
+}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/search/AssetSearchInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/search/AssetSearchInteractor.kt
index 1817c6eecd..3c4ec20485 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/search/AssetSearchInteractor.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/assets/search/AssetSearchInteractor.kt
@@ -1,148 +1,45 @@
package io.novafoundation.nova.feature_assets.domain.assets.search
-import io.novafoundation.nova.common.utils.flowOfAll
-import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
-import io.novafoundation.nova.feature_assets.domain.common.AssetGroup
-import io.novafoundation.nova.feature_assets.domain.common.AssetWithOffChainBalance
-import io.novafoundation.nova.feature_assets.domain.common.getAssetBaseComparator
-import io.novafoundation.nova.feature_assets.domain.common.getAssetGroupBaseComparator
-import io.novafoundation.nova.feature_assets.domain.common.groupAndSortAssetsByNetwork
-import io.novafoundation.nova.feature_assets.domain.common.searchTokens
-import io.novafoundation.nova.feature_swap_api.domain.swap.SwapService
-import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry
-import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
+import io.novafoundation.nova.feature_assets.domain.assets.models.AssetsByViewModeResult
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
import io.novafoundation.nova.feature_wallet_api.domain.model.ExternalBalance
-import io.novafoundation.nova.feature_wallet_api.domain.model.aggregatedBalanceByAsset
-import io.novafoundation.nova.runtime.ext.fullId
-import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
-import io.novafoundation.nova.runtime.multiNetwork.ChainsById
-import io.novafoundation.nova.runtime.multiNetwork.asset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
-import io.novafoundation.nova.runtime.multiNetwork.enabledChainById
-import io.novasama.substrate_sdk_android.hash.isPositive
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-private typealias AssetSearchFilter = suspend (Asset) -> Boolean
+interface AssetSearchInteractorFactory {
-class AssetSearchInteractor(
- private val walletRepository: WalletRepository,
- private val accountRepository: AccountRepository,
- private val chainRegistry: ChainRegistry,
- private val assetSourceRegistry: AssetSourceRegistry,
- private val swapService: SwapService
-) {
+ fun createByAssetViewMode(): AssetSearchInteractor
+}
+
+typealias AssetSearchFilter = suspend (Asset) -> Boolean
+
+interface AssetSearchInteractor {
fun buyAssetSearch(
queryFlow: Flow,
externalBalancesFlow: Flow>,
- ): Flow