Skip to content

Commit

Permalink
Merge pull request #1779 from novasamatech/feature/popular-dapps
Browse files Browse the repository at this point in the history
Feature/popular dapps
  • Loading branch information
antonijzelinskij authored Jan 31, 2025
2 parents 984de94 + 033d3f3 commit 301df74
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 20 deletions.
3 changes: 3 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="TypographyEllipsis">

<string name="popular_dapps_title">Popular DApps</string>

<string name="swap_intermediate_too_low_amount_to_stay_abow_ed_message">During swap execution intermediate receive amount is %s which is less than minimum balance of %s. Try specifying larger swap amount.</string>

<string name="common_not_enough_to_pay_fee_message">You don\'t have enough balance to pay network fee of %s. Current balance is %s</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.novafoundation.nova.feature_dapp_api.data.model

import io.novafoundation.nova.common.list.GroupedList

class DApp(
val name: String,
val description: String,
Expand All @@ -8,3 +10,8 @@ class DApp(
val isFavourite: Boolean,
val favoriteIndex: Int?
)

data class DAppGroupedCatalog(
val popular: List<DApp>,
val categoriesWithDApps: GroupedList<DappCategory, DApp>
)
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package io.novafoundation.nova.feature_dapp_api.data.model

typealias DAppUrl = String

class DappCatalog(
val popular: List<DAppUrl>,
val categories: List<DappCategory>,
val dApps: List<DappMetadata>
)

class DappMetadata(
val name: String,
val iconLink: String,
val url: String,
val url: DAppUrl,
val baseUrl: String,
val categories: Set<DappCategory>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ fun mapDAppMetadataResponseToDAppMetadatas(
iconLink = it.icon,
url = it.url,
baseUrl = Urls.normalizeUrl(it.url),
categories = it.categories.mapNotNullTo(mutableSetOf(), categoriesAssociatedById::get)
categories = it.categories.mapNotNullTo(mutableSetOf(), categoriesAssociatedById::get),
)
}

return DappCatalog(categories, metadata)
return DappCatalog(response.popular.map { it.url }, categories, metadata)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.novafoundation.nova.feature_dapp_impl.data.network.metadata

class DappMetadataResponse(
val popular: List<DappPopularRemote>,
val categories: List<DappCategoryRemote>,
val dapps: List<DappMetadataRemote>
)
Expand All @@ -18,3 +19,5 @@ class DappCategoryRemote(
val name: String,
val id: String
)

class DappPopularRemote(val url: String)
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.novafoundation.nova.feature_dapp_impl.domain

import io.novafoundation.nova.common.list.GroupedList
import io.novafoundation.nova.common.utils.Urls
import io.novafoundation.nova.feature_dapp_api.data.model.DApp
import io.novafoundation.nova.feature_dapp_api.data.model.DAppGroupedCatalog
import io.novafoundation.nova.feature_dapp_api.data.model.DAppUrl
import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory
import io.novafoundation.nova.feature_dapp_api.data.model.DappMetadata
import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository
import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp
import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository
Expand All @@ -20,6 +22,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.withContext
import kotlin.random.Random

class DappInteractor(
private val dAppMetadataRepository: DAppMetadataRepository,
Expand Down Expand Up @@ -51,25 +54,44 @@ class DappInteractor(
.map { it.sortDApps() }
}

fun observeDAppsByCategory(): Flow<GroupedList<DappCategory, DApp>> {
fun observeDAppsByCategory(): Flow<DAppGroupedCatalog> {
val shufflingSeed = Random.nextInt()

return combine(
dAppMetadataRepository.observeDAppCatalog(),
favouritesDAppRepository.observeFavourites()
) { dAppCatalog, favourites ->
// We use random with seed to shuffle dapps in categories the same way during updates
val random = Random(shufflingSeed)

val categories = dAppCatalog.categories
val dapps = dAppCatalog.dApps

val urlToDAppMapping = buildUrlToDappMapping(dapps, favourites)

// Regrouping in O(Categories * Dapps)
// Complexity should be fine for expected amount of dApps
categories.associateWith { category ->
dapps.filter { category in it.categories }
.map { urlToDAppMapping.getValue(it.url) }
}
val popular = dAppCatalog.popular.mapNotNull { urlToDAppMapping[it] }
val catalog = categories.associateWith { getShuffledDAppsInCategory(it, dapps, urlToDAppMapping, dAppCatalog.popular.toSet(), random) }

DAppGroupedCatalog(popular, catalog)
}
}

private fun getShuffledDAppsInCategory(
category: DappCategory,
dapps: List<DappMetadata>,
urlToDAppMapping: Map<String, DApp>,
popular: Set<DAppUrl>,
shufflingSeed: Random
): List<DApp> {
val categoryDApps = dapps.filter { category in it.categories }
.map { urlToDAppMapping.getValue(it.url) }

val popularDAppsInCategory = categoryDApps.filter { it.url in popular }
val otherDAppsInCategory = categoryDApps.filterNot { it.url in popular }

return popularDAppsInCategory.shuffled(shufflingSeed) + otherDAppsInCategory.shuffled(shufflingSeed)
}

suspend fun getDAppInfo(dAppUrl: String): DAppInfo {
val baseUrl = Urls.normalizeUrl(dAppUrl)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.novafoundation.nova.feature_dapp_impl.presentation.common

import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_dapp_api.data.model.DApp
import io.novafoundation.nova.feature_dapp_api.data.model.DAppGroupedCatalog
import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory
import io.novafoundation.nova.feature_dapp_impl.R
import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp
import io.novafoundation.nova.feature_dapp_impl.domain.common.dappToFavorite
import io.novafoundation.nova.feature_dapp_impl.domain.common.favouriteToDApp
Expand All @@ -17,8 +20,15 @@ data class DappModel(

fun mapDappCategoriesToDescription(categories: Collection<DappCategory>) = categories.joinToString { it.name }

fun mapDappCategoryToDappCategoryModel(category: DappCategory, dApps: List<DApp>) = DappCategoryModel(
categoryName = category.name,
fun mapDAppCatalogToDAppCategoryModels(resourceManager: ResourceManager, dappCatalog: DAppGroupedCatalog): List<DappCategoryModel> {
val popular = mapDappCategoryToDappCategoryModel(resourceManager.getString(R.string.popular_dapps_title), dappCatalog.popular)
val categories = dappCatalog.categoriesWithDApps.map { (category, dapps) -> mapDappCategoryToDappCategoryModel(category.name, dapps) }

return listOf(popular) + categories
}

fun mapDappCategoryToDappCategoryModel(categoryName: String, dApps: List<DApp>) = DappCategoryModel(
categoryName = categoryName,
items = dApps.map { mapDappToDappModel(it) }
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.mixin.api.Browserable
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.Event
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.common.utils.indexOfFirstOrNull
Expand All @@ -14,7 +15,7 @@ import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBro
import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor
import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel
import io.novafoundation.nova.feature_dapp_impl.presentation.common.dappCategoryToUi
import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDappCategoryToDappCategoryModel
import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDAppCatalogToDAppCategoryModels
import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapFavoriteDappToDappModel
import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryState
import kotlinx.coroutines.flow.filterNotNull
Expand All @@ -26,6 +27,7 @@ class MainDAppViewModel(
private val selectedAccountUseCase: SelectedAccountUseCase,
private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
private val dappInteractor: DappInteractor,
private val resourceManager: ResourceManager
) : BaseViewModel(), Browserable {

override val openBrowserEvent = MutableLiveData<Event<String>>()
Expand All @@ -41,9 +43,7 @@ class MainDAppViewModel(
.share()

private val groupedDAppsUiFlow = groupedDAppsFlow
.map {
it.map { (category, dapps) -> mapDappCategoryToDappCategoryModel(category, dapps) }
}
.map { mapDAppCatalogToDAppCategoryModels(resourceManager, it) }
.inBackground()
.share()

Expand All @@ -57,7 +57,7 @@ class MainDAppViewModel(
.share()

val categoriesStateFlow = groupedDAppsFlow
.map { catalog -> catalog.keys.map { dappCategoryToUi(it, isSelected = false) } }
.map { catalog -> catalog.categoriesWithDApps.keys.map { dappCategoryToUi(it, isSelected = false) } }
.map { categories ->
DAppCategoryState(
categories = categories,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dagger.multibindings.IntoMap
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor
Expand All @@ -29,13 +30,15 @@ class MainDAppModule {
selectedAccountUseCase: SelectedAccountUseCase,
actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
router: DAppRouter,
dappInteractor: DappInteractor
dappInteractor: DappInteractor,
resourceManager: ResourceManager
): ViewModel {
return MainDAppViewModel(
router = router,
selectedAccountUseCase = selectedAccountUseCase,
actionAwaitableMixinFactory = actionAwaitableMixinFactory,
dappInteractor = dappInteractor
dappInteractor = dappInteractor,
resourceManager = resourceManager
)
}
}

0 comments on commit 301df74

Please sign in to comment.