Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/colored asset icons fix #1704

Merged
merged 13 commits into from
Nov 1, 2024
3 changes: 3 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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\""
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.novafoundation.nova.common.data.model

enum class AssetIconMode {
COLORED, WHITE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext

interface AssetsIconModeRepository {
fun assetsIconModeFlow(): Flow<AssetIconMode>

suspend 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<AssetIconMode> {
return preferences.stringFlow(PREFS_ASSETS_ICON_MODE)
.map {
it?.fromPrefsValue() ?: ASSET_ICON_MODE_DEFAULT
}
}

override suspend fun setAssetsIconMode(assetsViewMode: AssetIconMode) = withContext(Dispatchers.IO) {
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
}
}
}
62 changes: 34 additions & 28 deletions common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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
Expand All @@ -30,6 +31,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
Expand Down Expand Up @@ -64,6 +66,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

valentunn marked this conversation as resolved.
Show resolved Hide resolved
fun computationalCache(): ComputationalCache

fun imageLoader(): ImageLoader
Expand Down Expand Up @@ -157,33 +189,7 @@ interface CommonApi {

fun assetsViewModeRepository(): AssetsViewModeRepository

val systemCallExecutor: SystemCallExecutor

val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory

val resourcesHintsMixinFactory: ResourcesHintsMixinFactory

val okHttpClient: OkHttpClient
fun assetsIconModeService(): AssetsIconModeRepository

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 assetIconProvider(): AssetIconProvider
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,8 +20,10 @@ 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
Expand All @@ -42,6 +45,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
Expand Down Expand Up @@ -352,5 +357,19 @@ class CommonModule {

@Provides
@ApplicationScope
fun assetsViewModeRepository(preferences: Preferences): AssetsViewModeRepository = RealAssetsViewModeRepository(preferences)
fun provideAssetsViewModeRepository(preferences: Preferences): AssetsViewModeRepository = RealAssetsViewModeRepository(preferences)

@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,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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 {

val fallbackIcon: Icon

fun getAssetIcon(iconName: String?, fallback: Icon = fallbackIcon): Icon

fun getWhiteAssetIcon(iconName: String?, fallback: Icon = fallbackIcon): Icon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave a few suggestions for the interface here:

  1. The idea of having fallback icon being exposed by AssetIconProvider looks good. However I think we can make the the interface clearer if getAssetIcon wont allow for nullable iconName. Instead, we can create an extension function that allows nullability and has the same signature that is currently present in the interface directly. This way we will simplify the interface itself
  2. Instead of creating a separate instance of getWhiteAssetIcon how about we make a function getAssetIcon(iconName, iconType) and implementgetWhiteAssetIcon in a form of extension? This way you wont need to icon formatting logic twice since getAssetIcon(iconName) can easily delegate its work to getAssetIcon(iconName, iconType) by passing iconType=selectedIconType

}

class RealAssetIconProvider(
private val assetsIconModeRepository: AssetsIconModeRepository,
private val coloredBaseUrl: String,
private val whiteBaseUrl: String
) : AssetIconProvider {

override val fallbackIcon: Icon = R.drawable.ic_nova.asIcon()

override fun getAssetIcon(iconName: String?, fallback: Icon): Icon {
if (iconName == null) return fallback

val iconUrl = when (assetsIconModeRepository.getIconMode()) {
AssetIconMode.COLORED -> "$coloredBaseUrl/$iconName"
AssetIconMode.WHITE -> "$whiteBaseUrl/$iconName"
}

return iconUrl.asIcon()
}

override fun getWhiteAssetIcon(iconName: String?, fallback: Icon): Icon {
return if (iconName == null) {
fallback
} else {
"$whiteBaseUrl/$iconName".asIcon()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions common/src/main/res/drawable/ic_token_dot_colored.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,24m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#E30079"/>
<path
android:pathData="M28.905,13.727C28.905,15.233 26.808,16.455 24.22,16.455C21.632,16.455 19.534,15.233 19.534,13.727C19.534,12.221 21.632,11 24.22,11C26.808,11 28.905,12.221 28.905,13.727Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M28.905,34.273C28.905,35.779 26.808,37 24.22,37C21.632,37 19.534,35.779 19.534,34.273C19.534,32.766 21.632,31.545 24.22,31.545C26.808,31.545 28.905,32.766 28.905,34.273Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M17.687,20.228C18.981,17.986 18.973,15.558 17.669,14.805C16.365,14.052 14.259,15.258 12.965,17.5C11.671,19.742 11.679,22.17 12.983,22.923C14.287,23.676 16.393,22.469 17.687,20.228Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M35.456,25.078C36.76,25.831 36.768,28.259 35.474,30.501C34.18,32.743 32.074,33.95 30.77,33.197C29.466,32.444 29.458,30.016 30.752,27.774C32.046,25.532 34.152,24.325 35.456,25.078Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M17.67,33.195C18.974,32.442 18.982,30.014 17.688,27.772C16.394,25.531 14.288,24.324 12.984,25.077C11.68,25.83 11.672,28.258 12.966,30.5C14.26,32.742 16.366,33.948 17.67,33.195Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M35.473,17.499C36.767,19.741 36.759,22.169 35.455,22.922C34.151,23.675 32.045,22.468 30.751,20.226C29.457,17.985 29.465,15.557 30.769,14.804C32.073,14.05 34.179,15.257 35.473,17.499Z"
android:fillColor="#ffffff"/>
</vector>
4 changes: 3 additions & 1 deletion common/src/main/res/layout/view_labeled_text.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_token_dot_colored"
tools:visibility="visible" />

<ImageView
android:id="@+id/labeledTextIcon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.widget.ImageView
import coil.ImageLoader
import coil.load
import coil.request.ImageRequest
import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.utils.images.Icon
import io.novafoundation.nova.common.utils.images.asIcon
import io.novafoundation.nova.feature_account_api.R
Expand Down Expand Up @@ -45,10 +46,6 @@ fun ImageView.loadTokenIcon(icon: String?, imageLoader: ImageLoader) {
}
}

fun Chain.Asset.icon(): Icon {
return iconUrl?.asIcon() ?: ASSET_ICON_PLACEHOLDER.asIcon()
}

fun Chain.iconOrFallback(): Icon {
return icon?.asIcon() ?: chainIconFallback()
}
Expand All @@ -60,3 +57,11 @@ fun String?.asIconOrFallback(): Icon {
fun chainIconFallback(): Icon {
return R.drawable.ic_fallback_network_icon.asIcon()
}

fun AssetIconProvider.getAssetIcon(asset: Chain.Asset, fallbackIcon: Icon = this.fallbackIcon): Icon {
return this.getAssetIcon(asset.icon, fallbackIcon)
}

fun AssetIconProvider.getWhiteAssetIcon(asset: Chain.Asset, fallbackIcon: Icon = this.fallbackIcon): Icon {
return this.getWhiteAssetIcon(asset.icon, fallbackIcon)
}
Loading
Loading