Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-15 17:25:50 +03:00
parent 7aa44c4da7
commit 8cb232cd36
11 changed files with 204 additions and 162 deletions

View file

@ -9,7 +9,6 @@ import arrow.core.toNonEmptyListOrNull
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -23,7 +22,7 @@ class ApplyTokenListSortingUseCase(
suspend operator fun invoke(
userWalletId: UserWalletId,
sortedTokensIds: List<Pair<Network.ID, CryptoCurrency.ID>>,
sortedTokensIds: List<CryptoCurrency.ID>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
): Either<TokenListSortingError, Unit> {
@ -40,7 +39,7 @@ class ApplyTokenListSortingUseCase(
}
private suspend fun Raise<TokenListSortingError>.sortTokens(
sortedTokensIds: List<Pair<Network.ID, CryptoCurrency.ID>>,
sortedTokensIds: List<CryptoCurrency.ID>,
unsortedTokens: List<CryptoCurrency>,
): List<CryptoCurrency> = withContext(dispatchers.default) {
val nonEmptySortedTokensIds = ensureNotNull(sortedTokensIds.toNonEmptySetOrNull()) {
@ -49,13 +48,13 @@ class ApplyTokenListSortingUseCase(
val sortedTokens = sortedMapOf<Int, CryptoCurrency>()
unsortedTokens.distinct().forEach { token ->
val index = nonEmptySortedTokensIds.indexOfFirst { (networkId, tokenId) ->
networkId == token.networkId && tokenId == token.id
unsortedTokens.distinct().forEach { currency ->
val index = nonEmptySortedTokensIds.indexOfFirst { currencyId ->
currencyId == currency.id
}
if (index >= 0) {
sortedTokens[index] = token
sortedTokens[index] = currency
} else {
raise(TokenListSortingError.UnableToSortTokenList)
}

View file

@ -54,7 +54,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
val result = useCase(
userWalletId = userWalletId,
sortedTokensIds = MockTokens.tokens.map { it.networkId to it.id },
sortedTokensIds = MockTokens.tokens.map { it.id },
isGroupedByNetwork = false,
isSortedByBalance = false,
)
@ -76,7 +76,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
useCase(
userWalletId = userWalletId,
sortedTokensIds = expectedTokens.map { it.networkId to it.id },
sortedTokensIds = expectedTokens.map { it.id },
isGroupedByNetwork = expectedIsGrouped,
isSortedByBalance = expectedIsSorted,
)
@ -100,7 +100,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
useCase(
userWalletId = userWalletId,
sortedTokensIds = expectedTokens.map { it.networkId to it.id },
sortedTokensIds = expectedTokens.map { it.id },
isGroupedByNetwork = expectedIsGrouped,
isSortedByBalance = expectedIsSorted,
)
@ -124,7 +124,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
useCase(
userWalletId = userWalletId,
sortedTokensIds = expectedTokens.map { it.networkId to it.id },
sortedTokensIds = expectedTokens.map { it.id },
isGroupedByNetwork = expectedIsGrouped,
isSortedByBalance = expectedIsSorted,
)
@ -148,7 +148,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
useCase(
userWalletId = userWalletId,
sortedTokensIds = expectedTokens.map { it.networkId to it.id },
sortedTokensIds = expectedTokens.map { it.id },
isGroupedByNetwork = expectedIsGrouped,
isSortedByBalance = expectedIsSorted,
)
@ -170,7 +170,7 @@ internal class ApplyTokenListSortingUseCaseTest {
// When
val result = useCase(
userWalletId = userWalletId,
sortedTokensIds = getSortedTokens().drop(n = 3).map { it.networkId to it.id },
sortedTokensIds = getSortedTokens().drop(n = 3).map { it.id },
isGroupedByNetwork = false,
isSortedByBalance = false,
)

View file

@ -9,6 +9,7 @@ plugins {
dependencies {
/** AndroidX */
implementation(deps.androidx.activity.compose)
implementation(deps.lifecycle.compose)
implementation(deps.material)
/** Compose */

View file

@ -1,8 +1,5 @@
package com.tangem.feature.wallet.presentation.organizetokens
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import org.burnoutcrew.reorderable.ItemPosition
internal interface OrganizeTokensIntents {
fun onBackClick()
@ -14,12 +11,4 @@ internal interface OrganizeTokensIntents {
fun onApplyClick()
fun onCancelClick()
fun onItemDragged(from: ItemPosition, to: ItemPosition)
fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean
fun onItemDraggingStart(item: DraggableItem)
fun onItemDraggingEnd()
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.organizetokens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@ -38,6 +39,8 @@ import org.burnoutcrew.reorderable.*
@Composable
internal fun OrganizeTokensScreen(state: OrganizeTokensState, modifier: Modifier = Modifier) {
BackHandler(onBack = state.onBackClick)
val tokensListState = rememberLazyListState()
Scaffold(

View file

@ -1,12 +1,12 @@
package com.tangem.feature.wallet.presentation.organizetokens
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.updateSorting
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.InProgressStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.TokenListToStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.error.TokenListErrorConverter
@ -17,22 +17,17 @@ import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.ite
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("unused", "MemberVisibilityCanBePrivate") // TODO: Will be used in next MR
internal class OrganizeTokensStateHolder(
private val intents: OrganizeTokensIntents,
private val fiatCurrencyCode: String,
private val fiatCurrencySymbol: String,
private val appCurrencyProvider: Provider<AppCurrency>,
private val onSubscription: () -> Unit,
scope: CoroutineScope,
stateFlowScope: CoroutineScope,
) {
private val stateFlowInternal: MutableStateFlow<OrganizeTokensState> = MutableStateFlow(getInitialState())
private val tokenListConverter by lazy {
val tokensConverter = CryptoCurrencyToDraggableItemConverter(
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
)
val tokensConverter = CryptoCurrencyToDraggableItemConverter(appCurrencyProvider)
val itemsConverter = TokenListToListStateConverter(
tokensConverter = tokensConverter,
groupsConverter = NetworkGroupToDraggableItemsConverter(tokensConverter),
@ -56,17 +51,13 @@ internal class OrganizeTokensStateHolder(
val stateFlow: StateFlow<OrganizeTokensState> = stateFlowInternal
.onSubscription { onSubscription() }
.stateIn(
scope = scope,
scope = stateFlowScope,
started = SharingStarted.WhileSubscribed(),
initialValue = getInitialState(),
)
var tokenList: TokenList? = null
private set
fun updateStateWithTokenList(tokenList: TokenList) {
updateState { tokenListConverter.convert(tokenList) }
this.tokenList = tokenList
}
fun updateStateToDisplayProgress() {
@ -77,16 +68,6 @@ internal class OrganizeTokensStateHolder(
updateState { inProgressStateConverter.convertBack(value = this) }
}
fun updateStateWithManualSorting(itemsState: OrganizeTokensListState) {
updateState {
copy(
header = header.copy(isSortedByBalance = false),
itemsState = itemsState,
)
}
tokenList = tokenList?.updateSorting(isSortedByBalance = false)
}
fun updateStateWithError(error: TokenListError) {
updateState { tokenListErrorConverter.convert(error) }
}
@ -107,11 +88,12 @@ internal class OrganizeTokensStateHolder(
onApplyClick = intents::onApplyClick,
onCancelClick = intents::onCancelClick,
),
// TODO: Will be added in next MR
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = intents::onItemDragged,
onDragStart = intents::onItemDraggingStart,
onItemDragEnd = intents::onItemDraggingEnd,
canDragItemOver = intents::canDragItemOver,
onItemDragged = { _, _ -> },
onDragStart = { },
onItemDragEnd = { },
canDragItemOver = { _, _ -> false },
),
)
}

View file

@ -1,133 +1,147 @@
package com.tangem.feature.wallet.presentation.organizetokens
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase
import com.tangem.domain.tokens.ToggleTokenListSortingUseCase
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.*
import com.tangem.feature.wallet.presentation.organizetokens.utils.CryptoCurrenciesIdsResolver
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.router.WalletRoute
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.burnoutcrew.reorderable.ItemPosition
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.properties.Delegates
// FIXME: Implemented with preview data
@HiltViewModel
internal class OrganizeTokensViewModel @Inject constructor(savedStateHandle: SavedStateHandle) : ViewModel() {
internal class OrganizeTokensViewModel @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
private val toggleTokenListGroupingUseCase: ToggleTokenListGroupingUseCase,
private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), OrganizeTokensIntents {
@Volatile
private var movingItem: DraggableItem? = null
lateinit var router: InnerWalletRouter
var router: InnerWalletRouter by Delegates.notNull()
val userWalletId: UserWalletId by lazy {
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
private val stateHolder = OrganizeTokensStateHolder(
stateFlowScope = viewModelScope,
intents = this,
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
onSubscription = {
bootstrapTokenList()
},
)
private val userWalletId: UserWalletId by lazy {
val userWalletIdValue: String = checkNotNull(savedStateHandle[WalletRoute.userWalletIdKey])
UserWalletId(userWalletIdValue)
}
var uiState: OrganizeTokensState by mutableStateOf(getInitialState())
private set
private var tokenList: TokenList? = null
private fun getInitialState(): OrganizeTokensState = WalletPreviewData.organizeTokensState.copy(
itemsState = OrganizeTokensListState.Ungrouped(
items = WalletPreviewData.draggableTokens,
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = this::moveItem,
canDragItemOver = this::checkCanMoveItemOver,
onItemDragEnd = this::endMoving,
onDragStart = this::startMoving,
),
header = OrganizeTokensState.HeaderConfig(
onSortClick = { /* no-op */ },
onGroupClick = this::toggleTokensByNetworkGrouping,
),
)
val uiState: StateFlow<OrganizeTokensState> = stateHolder.stateFlow
private fun toggleTokensByNetworkGrouping() {
val newListState = when (val itemsState = uiState.itemsState) {
is OrganizeTokensListState.GroupedByNetwork -> OrganizeTokensListState.Ungrouped(
items = itemsState.items.filterIsInstance<DraggableItem.Token>().toPersistentList(),
override fun onBackClick() {
router.popBackStack()
}
override fun onSortClick() {
viewModelScope.launch(Dispatchers.Default) {
val list = tokenList ?: return@launch
toggleTokenListSortingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateWithTokenList(it)
tokenList = it
},
)
is OrganizeTokensListState.Ungrouped -> OrganizeTokensListState.GroupedByNetwork(
items = WalletPreviewData.draggableItems,
}
}
override fun onGroupClick() {
viewModelScope.launch(Dispatchers.Default) {
val list = tokenList ?: return@launch
toggleTokenListGroupingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateWithTokenList(it)
tokenList = it
},
)
is OrganizeTokensListState.Empty -> itemsState
}
uiState = uiState.copy(itemsState = newListState)
}
private fun checkCanMoveItemOver(moveOverItemPosition: ItemPosition, movedItemPosition: ItemPosition): Boolean {
val items = (uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork)
?.items
?: return true // If ungrouped then item can be moved anywhere
val (moveOverItem, movedItem) = items.findItemsToMove(moveOverItemPosition.key, movedItemPosition.key)
if (moveOverItem == null || movedItem == null) {
return false
}
return when (movedItem) {
is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(moveOverItemPosition, moveOverItem, items.lastIndex)
is DraggableItem.Token -> checkCanMoveTokenOver(movedItem, moveOverItem)
is DraggableItem.GroupPlaceholder -> false
}
}
private fun startMoving(movingItem: DraggableItem) = viewModelScope.launch(Dispatchers.Default) {
if (this@OrganizeTokensViewModel.movingItem != null) return@launch
this@OrganizeTokensViewModel.movingItem = movingItem
override fun onApplyClick() {
viewModelScope.launch(Dispatchers.Default) {
stateHolder.updateStateToDisplayProgress()
val updatedItemsState = uiState.itemsState.updateItems { items ->
when (movingItem) {
is DraggableItem.GroupHeader -> items.collapseGroup(movingItem)
is DraggableItem.Token -> when (uiState.itemsState) {
is OrganizeTokensListState.GroupedByNetwork -> items.divideGroups(movingItem)
is OrganizeTokensListState.Ungrouped -> items.divideItems(movingItem)
is OrganizeTokensListState.Empty -> uiState.itemsState.items
}
is DraggableItem.GroupPlaceholder -> items
val listState = uiState.value.itemsState
val resolver = CryptoCurrenciesIdsResolver()
val result = applyTokenListSortingUseCase(
userWalletId = userWalletId,
sortedTokensIds = resolver.resolve(listState, tokenList),
isGroupedByNetwork = listState is OrganizeTokensListState.GroupedByNetwork,
isSortedByBalance = uiState.value.header.isSortedByBalance,
)
result.fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateToHideProgress()
withContext(Dispatchers.Main) { router.popBackStack() }
},
)
}
}
override fun onCancelClick() {
router.popBackStack()
}
private fun bootstrapTokenList() {
viewModelScope.launch(Dispatchers.Default) {
val maybeTokenList = getTokenListUseCase(userWalletId)
.first { it.getOrNull()?.totalFiatBalance is TokenList.FiatBalance.Loaded }
maybeTokenList.fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateWithTokenList(it)
tokenList = it
},
)
}
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
}
uiState = uiState.copy(itemsState = updatedItemsState)
}
private fun endMoving() = viewModelScope.launch(Dispatchers.Default) {
if (movingItem == null) return@launch
val updatedItemsState = uiState.itemsState.updateItems { items ->
when (movingItem) {
is DraggableItem.GroupHeader -> items.expandGroups()
is DraggableItem.Token -> items.uniteItems()
is DraggableItem.GroupPlaceholder,
null,
-> items
}
}
uiState = uiState.copy(itemsState = updatedItemsState)
movingItem = null
}
private fun moveItem(from: ItemPosition, to: ItemPosition) = viewModelScope.launch(Dispatchers.Default) {
uiState = uiState.copy(
itemsState = uiState.itemsState.updateItems {
it.moveItem(from.index, to.index)
},
)
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
internal class CryptoCurrenciesIdsResolver {
fun resolve(listState: OrganizeTokensListState, tokenList: TokenList?): List<CryptoCurrency.ID> {
val draggableTokens = when (listState) {
is OrganizeTokensListState.Empty -> return emptyList()
is OrganizeTokensListState.GroupedByNetwork -> listState.items.filterIsInstance<DraggableItem.Token>()
is OrganizeTokensListState.Ungrouped -> listState.items
}
val currenciesStatuses = when (tokenList) {
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies }
is TokenList.Ungrouped -> tokenList.currencies
is TokenList.NotInitialized,
null,
-> return emptyList()
}
return draggableTokens.mapNotNull { draggableToken ->
val currencyStatus = currenciesStatuses.firstOrNull {
it.currency.id.value == draggableToken.id
}
currencyStatus?.currency?.id
}
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import androidx.annotation.DrawableRes
import com.tangem.common.Provider
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.wallet.impl.R
@ -12,8 +14,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getTok
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyToDraggableItemConverter(
private val fiatCurrencyCode: String,
private val fiatCurrencySymbol: String,
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<CryptoCurrencyStatus, DraggableItem.Token> {
private val CryptoCurrency.networkIconResId: Int?
@ -29,13 +30,29 @@ internal class CryptoCurrencyToDraggableItemConverter(
}
override fun convert(value: CryptoCurrencyStatus): DraggableItem.Token {
return createDraggableToken(value, appCurrencyProvider())
}
override fun convertList(input: Collection<CryptoCurrencyStatus>): List<DraggableItem.Token> {
val appCurrency = appCurrencyProvider()
return input.map { createDraggableToken(it, appCurrency) }
}
private fun createDraggableToken(
currencyStatus: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): DraggableItem.Token {
return DraggableItem.Token(
tokenItemState = createToTokenItemState(value),
groupId = getGroupHeaderId(value.currency.networkId),
tokenItemState = createTokenItemState(currencyStatus, appCurrency),
groupId = getGroupHeaderId(currencyStatus.currency.networkId),
)
}
private fun createToTokenItemState(currencyStatus: CryptoCurrencyStatus): TokenItemState.Draggable {
private fun createTokenItemState(
currencyStatus: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): TokenItemState.Draggable {
val currency = currencyStatus.currency
return TokenItemState.Draggable(
@ -44,13 +61,13 @@ internal class CryptoCurrencyToDraggableItemConverter(
tokenIconResId = currency.tokenIconResId,
networkIconResId = currency.networkIconResId,
name = currency.name,
fiatAmount = getFormattedFiatAmount(currencyStatus),
fiatAmount = getFormattedFiatAmount(currencyStatus, appCurrency),
)
}
private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus): String {
private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
val fiatAmount = currency.value.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
return BigDecimalFormatter.formatFiatAmount(fiatAmount, fiatCurrencyCode, fiatCurrencySymbol)
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
}
}

View file

@ -1,13 +1,15 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
@ -59,9 +61,11 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
router = this@DefaultWalletRouter
}
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
OrganizeTokensScreen(
modifier = Modifier.systemBarsPadding(),
state = viewModel.uiState,
modifier = Modifier.statusBarsPadding(),
state = uiState,
)
}
}

View file

@ -132,6 +132,7 @@ androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.r
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidxLifecycle" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
lifecycle-viewModel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
# region AndroidX
# region Compose