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] Advanced features grouping #3563

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Card
Expand All @@ -25,10 +25,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ivy.design.system.colors.IvyColors.Gray
import com.ivy.navigation.navigation
import com.ivy.navigation.screenScopedViewModel
import kotlinx.collections.immutable.ImmutableList
Expand Down Expand Up @@ -63,7 +64,7 @@ private fun FeaturesUi(
content = { innerPadding ->
Content(
modifier = Modifier.padding(innerPadding),
features = uiState.features,
items = uiState.featureItemViewStates,
onToggleFeature = {
onEvent(FeaturesUiEvent.ToggleFeature(it))
}
Expand Down Expand Up @@ -104,8 +105,8 @@ private fun Title(

@Composable
private fun Content(
features: ImmutableList<FeatureUi>,
onToggleFeature: (Int) -> Unit,
items: ImmutableList<FeatureItemViewState>,
onToggleFeature: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
Expand All @@ -116,19 +117,26 @@ private fun Content(
),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(features) { index, item ->
FeatureRow(
feature = item,
onToggleClick = { onToggleFeature(index) }
)
items(items) { item ->
when (item) {
is FeatureItemViewState.FeatureHeaderViewState -> {
FeatureSectionDivider(text = item.name)
}

is FeatureItemViewState.FeatureToggleViewState -> {
FeatureRow(
feature = item,
onToggleClick = { onToggleFeature(item.key) }
)
}
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FeatureRow(
feature: FeatureUi,
feature: FeatureItemViewState.FeatureToggleViewState,
onToggleClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -159,3 +167,22 @@ private fun FeatureRow(
}
}
}

@Composable
private fun FeatureSectionDivider(
text: String,
color: Color = Gray
) {
Column {
Spacer(Modifier.height(16.dp))

Text(
modifier = Modifier.padding(start = 16.dp),
text = text,
style = MaterialTheme.typography.titleMedium.copy(
color = color,
fontWeight = FontWeight.Bold
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.ivy.features

sealed interface FeaturesUiEvent {
data class ToggleFeature(val index: Int) : FeaturesUiEvent
data class ToggleFeature(val key: String) : FeaturesUiEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ package com.ivy.features
import kotlinx.collections.immutable.ImmutableList

data class FeaturesUiState(
val features: ImmutableList<FeatureUi>,
val featureItemViewStates: ImmutableList<FeatureItemViewState>,
)

data class FeatureUi(
val name: String,
val enabled: Boolean,
val description: String?,
)
sealed interface FeatureItemViewState {
data class FeatureToggleViewState(
val key: String,
val name: String,
val enabled: Boolean,
val description: String?,
) : FeatureItemViewState

data class FeatureHeaderViewState(val name: String) : FeatureItemViewState
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.lifecycle.viewModelScope
import com.ivy.domain.features.BoolFeature
import com.ivy.domain.features.FeatureGroup
import com.ivy.ui.ComposeViewModel
import com.ivy.domain.features.Features
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -23,23 +25,43 @@ class FeaturesViewModel @Inject constructor(
@ApplicationContext
private val context: Context
) : ComposeViewModel<FeaturesUiState, FeaturesUiEvent>() {

@Composable
override fun uiState(): FeaturesUiState {
return FeaturesUiState(
features = getFeatures()
featureItemViewStates = getFeatures()
)
}

@SuppressLint("BuildListAdds")
@Composable
fun getFeatures(): ImmutableList<FeatureUi> {
val allFeatures = features.allFeatures.map {
FeatureUi(
name = it.name ?: it.key,
description = it.description,
enabled = it.asEnabledState()
)
}
return allFeatures.toImmutableList()
fun getFeatures(): ImmutableList<FeatureItemViewState> {
return buildList {
val groups: Map<FeatureGroup?, BoolFeature> =
features.allFeatures
.associateBy { it.group }
.toSortedMap(compareBy { it?.name })

groups.forEach { group ->
add(
FeatureItemViewState.FeatureHeaderViewState(
name = group.key?.name ?: "Undefined"
)
)
val featuresByGroup: List<FeatureItemViewState> = features
.allFeatures
.filter { it.group?.name == group.key?.name }
.map {
FeatureItemViewState.FeatureToggleViewState(
key = it.key,
name = it.name ?: it.key,
description = it.description,
enabled = it.asEnabledState()
)
}
addAll(featuresByGroup)
}
}.toImmutableList()
}

override fun onEvent(event: FeaturesUiEvent) {
Expand All @@ -50,9 +72,9 @@ class FeaturesViewModel @Inject constructor(

private fun toggleFeature(event: FeaturesUiEvent.ToggleFeature) {
viewModelScope.launch {
val feature = features.allFeatures[event.index]
val enabled = feature.enabledFlow(context).first() ?: false
feature.set(context, !enabled)
val feature = features.allFeatures.find { feature -> feature.key == event.key }
val enabled = feature?.enabledFlow(context)?.first() ?: false
feature?.set(context, !enabled)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.map
@Immutable
class BoolFeature(
val key: String,
val group: FeatureGroup? = null,
val name: String? = null,
val description: String? = null,
private val defaultValue: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.ivy.domain.features

enum class FeatureGroup {
Category, Account, Other
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,45 @@ class IvyFeatures @Inject constructor() : Features {

override val sortCategoriesAlphabetically = BoolFeature(
key = "sort_categories_alphabetically",
group = FeatureGroup.Other,
name = "Sort Categories Alphabetically",
description = "Sort income and expenses" +
" categories alphabetically"
)

override val compactAccountsMode = BoolFeature(
key = "compact_account_ui",
group = FeatureGroup.Account,
name = "Compact account UI",
description = "Enables more compact and dense UI for the \"Accounts\" tab"
)

override val compactCategoriesMode = BoolFeature(
key = "compact_categories_ui",
group = FeatureGroup.Category,
name = "Compact category UI",
description = "Activates a more streamlined and space-efficient interface for the \"Categories\" Screen"
)

override val showTitleSuggestions = BoolFeature(
key = "show_title_suggestions",
group = FeatureGroup.Other,
name = "Show previous title suggestions",
description = "Enables display of previous transaction titles when editing or creating a new transaction",
defaultValue = true
)

override val showCategorySearchBar = BoolFeature(
key = "search_categories",
group = FeatureGroup.Category,
name = "Search categories",
description = "Show search bar in category screen",
defaultValue = true
)

override val hideTotalBalance = BoolFeature(
key = "hide_total_balance",
group = FeatureGroup.Account,
name = "Hide total balance",
description = "Enable hide the total balance from the accounts tab",
defaultValue = false
Expand Down