diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt new file mode 100644 index 00000000..eb0decf0 --- /dev/null +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt @@ -0,0 +1,115 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.feature.settings.unitgroups + +import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS +import com.sadellie.unitto.data.model.UnitGroup +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.burnoutcrew.reorderable.ItemPosition +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Repository that holds information about shown and hidden [UnitGroup]s and provides methods to + * show/hide [UnitGroup]s. + */ +@Singleton +class UnitGroupsRepository @Inject constructor() { + + /** + * Mutex is need needed because we work with flow (sync stuff). + */ + private val mutex = Mutex() + + /** + * Currently shown [UnitGroup]s. + */ + var shownUnitGroups = MutableStateFlow(listOf()) + private set + + /** + * Currently hidden [UnitGroup]s. + */ + var hiddenUnitGroups = MutableStateFlow(listOf()) + private set + + /** + * Sets [shownUnitGroups] and updates [hiddenUnitGroups] as a side effect. [hiddenUnitGroups] is + * everything from [ALL_UNIT_GROUPS] that was not in [shownUnitGroups]. + * + * @param list List of [UnitGroup]s that need to be shown. + */ + suspend fun updateShownGroups(list: List) { + mutex.withLock { + shownUnitGroups.value = list + hiddenUnitGroups.value = ALL_UNIT_GROUPS - list.toSet() + } + } + + /** + * Moves [UnitGroup] from [shownUnitGroups] to [hiddenUnitGroups] + * + * @param unitGroup [UnitGroup] to hide. + */ + suspend fun markUnitGroupAsHidden(unitGroup: UnitGroup) { + mutex.withLock { + shownUnitGroups.value = shownUnitGroups.value - unitGroup + // Newly hidden unit will appear at the top of the list + hiddenUnitGroups.value = listOf(unitGroup) + hiddenUnitGroups.value + } + } + + /** + * Moves [UnitGroup] from [hiddenUnitGroups] to [shownUnitGroups] + * + * @param unitGroup [UnitGroup] to show. + */ + suspend fun markUnitGroupAsShown(unitGroup: UnitGroup) { + mutex.withLock { + hiddenUnitGroups.value = hiddenUnitGroups.value - unitGroup + shownUnitGroups.value = shownUnitGroups.value + unitGroup + } + } + + /** + * Moves [UnitGroup] in [shownUnitGroups] from one index to another (reorder). + * + * @param from Position from which we need to move from + * @param to Position where to put [UnitGroup] + */ + suspend fun moveShownUnitGroups(from: ItemPosition, to: ItemPosition) { + mutex.withLock { + shownUnitGroups.value = shownUnitGroups.value.toMutableList().apply { + val initialIndex = shownUnitGroups.value.indexOfFirst { it == from.key } + /** + * No such item. Happens when dragging item and clicking "remove" while item is + * still being dragged. + */ + if (initialIndex == -1) return + + add( + shownUnitGroups.value.indexOfFirst { it == to.key }, + removeAt(initialIndex) + ) + } + } + } +} diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt index 520631ee..6956c7ed 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -47,7 +48,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.ui.common.Header import com.sadellie.unitto.core.ui.common.NavigateUpButton @@ -63,17 +63,18 @@ internal fun UnitGroupsScreen( viewModel: UnitGroupsViewModel = hiltViewModel(), navigateUpAction: () -> Unit ) { - val uiState = viewModel.uiState.collectAsStateWithLifecycle() - UnittoScreenWithLargeTopBar( title = stringResource(R.string.unit_groups_setting), navigationIcon = { NavigateUpButton(navigateUpAction) } ) { paddingValues -> + val shownUnits = viewModel.shownUnitGroups.collectAsState() + val hiddenUnits = viewModel.hiddenUnitGroups.collectAsState() + val state = rememberReorderableLazyListState( - onMove = viewModel::moveShownUnitGroups, + onMove = viewModel::onMove, canDragOver = { from, _ -> viewModel.canDragOver(from) }, - onDragEnd = { _, _ -> viewModel.saveShownUnitGroups() } + onDragEnd = { _, _ -> viewModel.onDragEnd() } ) LazyColumn( @@ -89,7 +90,7 @@ internal fun UnitGroupsScreen( ) } - items(uiState.value.shownGroups, { it }) { item -> + items(shownUnits.value, { it }) { item -> ReorderableItem(state, key = item) { isDragging -> val transition = updateTransition(isDragging, label = "draggedTransition") val background by transition.animateColor(label = "background") { @@ -104,7 +105,7 @@ internal fun UnitGroupsScreen( modifier = Modifier .padding(horizontal = itemPadding) .clip(CircleShape) - .clickable { viewModel.markUnitGroupAsHidden(item) } + .clickable { viewModel.hideUnitGroup(item) } .detectReorderAfterLongPress(state), colors = ListItemDefaults.colors( containerColor = background @@ -117,7 +118,7 @@ internal fun UnitGroupsScreen( modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(false), - onClick = { viewModel.markUnitGroupAsHidden(item) } + onClick = { viewModel.hideUnitGroup(item) } ) ) }, @@ -147,11 +148,11 @@ internal fun UnitGroupsScreen( ) } - items(uiState.value.hiddenGroups, { it }) { + items(hiddenUnits.value, { it }) { ListItem( modifier = Modifier .background(MaterialTheme.colorScheme.surface) - .clickable { viewModel.markUnitGroupAsShown(it) } + .clickable { viewModel.returnUnitGroup(it) } .animateItemPlacement(), headlineContent = { Text(stringResource(it.res)) }, trailingContent = { @@ -162,7 +163,7 @@ internal fun UnitGroupsScreen( modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(false), - onClick = { viewModel.markUnitGroupAsShown(it) } + onClick = { viewModel.returnUnitGroup(it) } ) ) } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt deleted file mode 100644 index 3051e863..00000000 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Unitto is a unit converter for Android - * Copyright (c) 2023 Elshan Agaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sadellie.unitto.feature.settings.unitgroups - -import com.sadellie.unitto.data.model.UnitGroup - -data class UnitGroupsUIState( - val shownGroups: List, - val hiddenGroups: List -) diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt index 3b6fd155..229f6119 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt @@ -20,116 +20,75 @@ package com.sadellie.unitto.feature.settings.unitgroups import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import org.burnoutcrew.reorderable.ItemPosition import javax.inject.Inject @HiltViewModel class UnitGroupsViewModel @Inject constructor( - private val userPreferencesRepository: UserPreferencesRepository, + private val userPrefsRepository: UserPreferencesRepository, + private val unitGroupsRepository: UnitGroupsRepository, ) : ViewModel() { - - private var mutex: Mutex = Mutex() + val shownUnitGroups = unitGroupsRepository.shownUnitGroups + val hiddenUnitGroups = unitGroupsRepository.hiddenUnitGroups /** - * Currently shown [UnitGroup]s. + * @see UnitGroupsRepository.markUnitGroupAsHidden + * @see UserPreferencesRepository.updateShownUnitGroups */ - private val _shownUnitGroups = MutableStateFlow(listOf()) + fun hideUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + unitGroupsRepository.markUnitGroupAsHidden(unitGroup) + userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) + } + } /** - * Currently hidden [UnitGroup]s. + * @see UnitGroupsRepository.markUnitGroupAsShown + * @see UserPreferencesRepository.updateShownUnitGroups */ - private val _hiddenUnitGroups = MutableStateFlow(listOf()) + fun returnUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + unitGroupsRepository.markUnitGroupAsShown(unitGroup) + userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) + } + } + + /** + * @see UnitGroupsRepository.moveShownUnitGroups + */ + fun onMove(from: ItemPosition, to: ItemPosition) { + viewModelScope.launch { + unitGroupsRepository.moveShownUnitGroups(from, to) + } + } + + /** + * @see UserPreferencesRepository.updateShownUnitGroups + */ + fun onDragEnd() { + viewModelScope.launch { + userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) + } + } + + /** + * Prevent from dragging over non-draggable items (headers and hidden) + * + * @param pos Position we are dragging over. + * @return True if can drag over given item. + */ + fun canDragOver(pos: ItemPosition) = shownUnitGroups.value.any { it == pos.key } init { viewModelScope.launch { - val shown = userPreferencesRepository.mainPreferencesFlow.first().shownUnitGroups - mutex.withLock { - _shownUnitGroups.update { shown } - _hiddenUnitGroups.update { ALL_UNIT_GROUPS - shown.toSet() } - } + unitGroupsRepository.updateShownGroups( + userPrefsRepository.mainPreferencesFlow.first().shownUnitGroups + ) } } - - val uiState = combine(_shownUnitGroups, _hiddenUnitGroups) { shown, hidden -> - return@combine UnitGroupsUIState( - shownGroups = shown, - hiddenGroups = hidden - ) - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(5000L), - UnitGroupsUIState(emptyList(), emptyList()) - ) - - /** - * Moves [UnitGroup] from [_shownUnitGroups] to [_hiddenUnitGroups] - * - * @param unitGroup [UnitGroup] to hide. - */ - fun markUnitGroupAsHidden(unitGroup: UnitGroup) = viewModelScope.launch { - mutex.withLock { - _shownUnitGroups.update { it - unitGroup } - // Newly hidden unit will appear at the top of the list - _hiddenUnitGroups.update { listOf(unitGroup) + it } - } - } - - /** - * Moves [UnitGroup] from [_hiddenUnitGroups] to [_shownUnitGroups] - * - * @param unitGroup [UnitGroup] to show. - */ - fun markUnitGroupAsShown(unitGroup: UnitGroup) = viewModelScope.launch { - mutex.withLock { - _hiddenUnitGroups.update { it - unitGroup } - _shownUnitGroups.update { it + unitGroup } - } - } - - /** - * Moves [UnitGroup] in [_shownUnitGroups] from one index to another (reorder). - * - * @param from Position from which we need to move from - * @param to Position where to put [UnitGroup] - */ - fun moveShownUnitGroups(from: ItemPosition, to: ItemPosition) = viewModelScope.launch { - mutex.withLock { - _shownUnitGroups.update { shown -> - shown.toMutableList().apply { - val initialIndex = shown.indexOfFirst { it == from.key } - /** - * No such item. Happens when dragging item and clicking "remove" while item is - * still being dragged. - */ - if (initialIndex == -1) return@launch - - add( - shown.indexOfFirst { it == to.key }, - removeAt(initialIndex) - ) - } - } - } - } - - fun canDragOver(pos: ItemPosition) = uiState.value.shownGroups.any { it == pos.key } - - fun saveShownUnitGroups() = viewModelScope.launch { - userPreferencesRepository.updateShownUnitGroups( - uiState.value.shownGroups - ) - } }