From 6847c9e9fda0314832fc490f3c0c40fca4d9d505 Mon Sep 17 00:00:00 2001 From: shamim-emon Date: Thu, 26 Sep 2024 12:18:52 +0600 Subject: [PATCH 1/3] Updated AdvanceD Feature UI --- screen/features/build.gradle.kts | 2 + .../java/com/ivy/features/FeaturesScreen.kt | 51 +++++++++++++++---- .../java/com/ivy/features/FeaturesUiEvent.kt | 2 +- .../java/com/ivy/features/FeaturesUiState.kt | 11 ++-- .../com/ivy/features/FeaturesViewModel.kt | 30 +++++++---- .../com/ivy/domain/features/BoolFeature.kt | 1 + .../com/ivy/domain/features/FeatureGroup.kt | 5 ++ .../com/ivy/domain/features/IvyFeatures.kt | 6 +++ 8 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt diff --git a/screen/features/build.gradle.kts b/screen/features/build.gradle.kts index a81ae19644..3d18945859 100644 --- a/screen/features/build.gradle.kts +++ b/screen/features/build.gradle.kts @@ -12,4 +12,6 @@ dependencies { implementation(projects.shared.ui.core) implementation(projects.shared.ui.core) implementation(projects.shared.ui.navigation) + implementation(projects.temp.legacyCode) + implementation(projects.temp.oldDesign) } \ No newline at end of file diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt index 7747750066..7afb66bf78 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt @@ -20,17 +20,20 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.ivy.design.l0_system.UI +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.l0_system.style import com.ivy.navigation.navigation import com.ivy.navigation.screenScopedViewModel +import com.ivy.wallet.ui.theme.Gray +import com.ivy.wallet.ui.theme.components.IvySwitch import kotlinx.collections.immutable.ImmutableList @Composable @@ -104,8 +107,8 @@ private fun Title( @Composable private fun Content( - features: ImmutableList, - onToggleFeature: (Int) -> Unit, + features: ImmutableList, + onToggleFeature: (String) -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( @@ -117,18 +120,25 @@ private fun Content( verticalArrangement = Arrangement.spacedBy(8.dp) ) { itemsIndexed(features) { index, item -> - FeatureRow( - feature = item, - onToggleClick = { onToggleFeature(index) } - ) + when (item) { + is FeatureHeader -> { + FeatureSectionDivider(text = item.name) + } + + is FeatureItem -> { + FeatureRow( + feature = item, + onToggleClick = { onToggleFeature(item.key) } + ) + } + } } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun FeatureRow( - feature: FeatureUi, + feature: FeatureItem, onToggleClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -155,7 +165,26 @@ private fun FeatureRow( } } Spacer(modifier = Modifier.width(8.dp)) - Switch(checked = feature.enabled, onCheckedChange = { onToggleClick() }) + IvySwitch(enabled = feature.enabled, onEnabledChange = { onToggleClick() }) } } } + +@Composable +private fun FeatureSectionDivider( + text: String, + color: Color = Gray +) { + Column { + Spacer(Modifier.height(32.dp)) + + Text( + modifier = Modifier.padding(start = 32.dp), + text = text, + style = UI.typo.b2.style( + color = color, + fontWeight = FontWeight.Bold + ) + ) + } +} diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesUiEvent.kt b/screen/features/src/main/java/com/ivy/features/FeaturesUiEvent.kt index 74d7006073..61af85dfa8 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesUiEvent.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesUiEvent.kt @@ -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 } diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt b/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt index ee51b979de..01888f0e1f 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt @@ -3,11 +3,16 @@ package com.ivy.features import kotlinx.collections.immutable.ImmutableList data class FeaturesUiState( - val features: ImmutableList, + val features: ImmutableList, ) -data class FeatureUi( +data class FeatureItem( + val key: String, val name: String, val enabled: Boolean, val description: String?, -) +) : Feature + +data class FeatureHeader(val name: String) : Feature + +interface Feature diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt b/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt index 923e14c1da..316734b3ad 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt @@ -31,14 +31,26 @@ class FeaturesViewModel @Inject constructor( } @Composable - fun getFeatures(): ImmutableList { - val allFeatures = features.allFeatures.map { - FeatureUi( - name = it.name ?: it.key, - description = it.description, - enabled = it.asEnabledState() - ) + fun getFeatures(): ImmutableList { + val groups = + features.allFeatures.distinctBy { it.group }.map { it.group?.name ?: "" }.sorted() + val allFeatures: MutableList = mutableListOf() + groups.forEach { group -> + allFeatures.add(FeatureHeader(name = group.toLowerCase().capitalize())) + val featuresByGroup: List = features + .allFeatures + .filter { it.group?.name == group } + .map { + FeatureItem( + key = it.key, + name = it.name ?: it.key, + description = it.description, + enabled = it.asEnabledState() + ) + } + allFeatures.addAll(featuresByGroup) } + return allFeatures.toImmutableList() } @@ -50,8 +62,8 @@ 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 + val feature = features.allFeatures.find { feature -> feature.key == event.key } + val enabled = feature!!.enabledFlow(context).first() ?: false feature.set(context, !enabled) } } diff --git a/shared/domain/src/main/java/com/ivy/domain/features/BoolFeature.kt b/shared/domain/src/main/java/com/ivy/domain/features/BoolFeature.kt index f12e7a86dd..2f3907c382 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/BoolFeature.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/BoolFeature.kt @@ -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 diff --git a/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt b/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt new file mode 100644 index 0000000000..cfef856212 --- /dev/null +++ b/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt @@ -0,0 +1,5 @@ +package com.ivy.domain.features + +enum class FeatureGroup { + CATEGORY, ACCOUNT, OTHER +} \ No newline at end of file diff --git a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt index a946c8a23e..4cec178c4d 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt @@ -8,6 +8,7 @@ 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" @@ -15,18 +16,21 @@ class IvyFeatures @Inject constructor() : Features { 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 @@ -34,6 +38,7 @@ class IvyFeatures @Inject constructor() : Features { override val showCategorySearchBar = BoolFeature( key = "search_categories", + group = FeatureGroup.CATEGORY, name = "Search categories", description = "Show search bar in category screen", defaultValue = true @@ -41,6 +46,7 @@ class IvyFeatures @Inject constructor() : Features { 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 From 920a492e4d70371dc91626873c7bcc1aa7d17360 Mon Sep 17 00:00:00 2001 From: shamim-emon Date: Thu, 26 Sep 2024 19:39:32 +0600 Subject: [PATCH 2/3] Reviews resolved --- screen/features/build.gradle.kts | 2 - .../java/com/ivy/features/FeaturesScreen.kt | 28 +++++----- .../java/com/ivy/features/FeaturesUiState.kt | 20 +++---- .../com/ivy/features/FeaturesViewModel.kt | 56 +++++++++++-------- .../com/ivy/domain/features/FeatureGroup.kt | 2 +- .../com/ivy/domain/features/IvyFeatures.kt | 12 ++-- 6 files changed, 63 insertions(+), 57 deletions(-) diff --git a/screen/features/build.gradle.kts b/screen/features/build.gradle.kts index 3d18945859..a81ae19644 100644 --- a/screen/features/build.gradle.kts +++ b/screen/features/build.gradle.kts @@ -12,6 +12,4 @@ dependencies { implementation(projects.shared.ui.core) implementation(projects.shared.ui.core) implementation(projects.shared.ui.navigation) - implementation(projects.temp.legacyCode) - implementation(projects.temp.oldDesign) } \ No newline at end of file diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt index 7afb66bf78..3374f0434b 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt @@ -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 @@ -20,20 +20,18 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.ivy.design.l0_system.UI 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 com.ivy.design.l0_system.style +import com.ivy.design.system.colors.IvyColors.Gray import com.ivy.navigation.navigation import com.ivy.navigation.screenScopedViewModel -import com.ivy.wallet.ui.theme.Gray -import com.ivy.wallet.ui.theme.components.IvySwitch import kotlinx.collections.immutable.ImmutableList @Composable @@ -66,7 +64,7 @@ private fun FeaturesUi( content = { innerPadding -> Content( modifier = Modifier.padding(innerPadding), - features = uiState.features, + featureItemViewStates = uiState.featureItemViewStates, onToggleFeature = { onEvent(FeaturesUiEvent.ToggleFeature(it)) } @@ -107,7 +105,7 @@ private fun Title( @Composable private fun Content( - features: ImmutableList, + featureItemViewStates: ImmutableList, onToggleFeature: (String) -> Unit, modifier: Modifier = Modifier, ) { @@ -119,13 +117,13 @@ private fun Content( ), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - itemsIndexed(features) { index, item -> + items(featureItemViewStates) { item -> when (item) { - is FeatureHeader -> { + is FeatureItemViewState.FeatureHeaderViewState -> { FeatureSectionDivider(text = item.name) } - is FeatureItem -> { + is FeatureItemViewState.FeatureToggleViewState -> { FeatureRow( feature = item, onToggleClick = { onToggleFeature(item.key) } @@ -138,7 +136,7 @@ private fun Content( @Composable private fun FeatureRow( - feature: FeatureItem, + feature: FeatureItemViewState.FeatureToggleViewState, onToggleClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -165,7 +163,7 @@ private fun FeatureRow( } } Spacer(modifier = Modifier.width(8.dp)) - IvySwitch(enabled = feature.enabled, onEnabledChange = { onToggleClick() }) + Switch(checked = feature.enabled, onCheckedChange = { onToggleClick() }) } } } @@ -176,12 +174,12 @@ private fun FeatureSectionDivider( color: Color = Gray ) { Column { - Spacer(Modifier.height(32.dp)) + Spacer(Modifier.height(16.dp)) Text( - modifier = Modifier.padding(start = 32.dp), + modifier = Modifier.padding(start = 16.dp), text = text, - style = UI.typo.b2.style( + style = MaterialTheme.typography.titleMedium.copy( color = color, fontWeight = FontWeight.Bold ) diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt b/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt index 01888f0e1f..611c0154b3 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesUiState.kt @@ -3,16 +3,16 @@ package com.ivy.features import kotlinx.collections.immutable.ImmutableList data class FeaturesUiState( - val features: ImmutableList, + val featureItemViewStates: ImmutableList, ) -data class FeatureItem( - val key: String, - val name: String, - val enabled: Boolean, - val description: String?, -) : Feature +sealed interface FeatureItemViewState { + data class FeatureToggleViewState( + val key: String, + val name: String, + val enabled: Boolean, + val description: String?, + ) : FeatureItemViewState -data class FeatureHeader(val name: String) : Feature - -interface Feature + data class FeatureHeaderViewState(val name: String) : FeatureItemViewState +} diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt b/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt index 316734b3ad..4a470fac9a 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesViewModel.kt @@ -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 @@ -23,35 +25,43 @@ class FeaturesViewModel @Inject constructor( @ApplicationContext private val context: Context ) : ComposeViewModel() { + @Composable override fun uiState(): FeaturesUiState { return FeaturesUiState( - features = getFeatures() + featureItemViewStates = getFeatures() ) } + @SuppressLint("BuildListAdds") @Composable - fun getFeatures(): ImmutableList { - val groups = - features.allFeatures.distinctBy { it.group }.map { it.group?.name ?: "" }.sorted() - val allFeatures: MutableList = mutableListOf() - groups.forEach { group -> - allFeatures.add(FeatureHeader(name = group.toLowerCase().capitalize())) - val featuresByGroup: List = features - .allFeatures - .filter { it.group?.name == group } - .map { - FeatureItem( - key = it.key, - name = it.name ?: it.key, - description = it.description, - enabled = it.asEnabledState() - ) - } - allFeatures.addAll(featuresByGroup) - } + fun getFeatures(): ImmutableList { + return buildList { + val groups: Map = + features.allFeatures + .associateBy { it.group } + .toSortedMap(compareBy { it?.name }) - return allFeatures.toImmutableList() + groups.forEach { group -> + add( + FeatureItemViewState.FeatureHeaderViewState( + name = group.key?.name ?: "Undefined" + ) + ) + val featuresByGroup: List = 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) { @@ -63,8 +73,8 @@ class FeaturesViewModel @Inject constructor( private fun toggleFeature(event: FeaturesUiEvent.ToggleFeature) { viewModelScope.launch { val feature = features.allFeatures.find { feature -> feature.key == event.key } - val enabled = feature!!.enabledFlow(context).first() ?: false - feature.set(context, !enabled) + val enabled = feature?.enabledFlow(context)?.first() ?: false + feature?.set(context, !enabled) } } } diff --git a/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt b/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt index cfef856212..44bece1829 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/FeatureGroup.kt @@ -1,5 +1,5 @@ package com.ivy.domain.features enum class FeatureGroup { - CATEGORY, ACCOUNT, OTHER + Category, Account, Other } \ No newline at end of file diff --git a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt index 4cec178c4d..c2f80701c7 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt @@ -8,7 +8,7 @@ class IvyFeatures @Inject constructor() : Features { override val sortCategoriesAlphabetically = BoolFeature( key = "sort_categories_alphabetically", - group = FeatureGroup.OTHER, + group = FeatureGroup.Other, name = "Sort Categories Alphabetically", description = "Sort income and expenses" + " categories alphabetically" @@ -16,21 +16,21 @@ class IvyFeatures @Inject constructor() : Features { override val compactAccountsMode = BoolFeature( key = "compact_account_ui", - group = FeatureGroup.ACCOUNT, + 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, + 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, + group = FeatureGroup.Other, name = "Show previous title suggestions", description = "Enables display of previous transaction titles when editing or creating a new transaction", defaultValue = true @@ -38,7 +38,7 @@ class IvyFeatures @Inject constructor() : Features { override val showCategorySearchBar = BoolFeature( key = "search_categories", - group = FeatureGroup.CATEGORY, + group = FeatureGroup.Category, name = "Search categories", description = "Show search bar in category screen", defaultValue = true @@ -46,7 +46,7 @@ class IvyFeatures @Inject constructor() : Features { override val hideTotalBalance = BoolFeature( key = "hide_total_balance", - group = FeatureGroup.ACCOUNT, + group = FeatureGroup.Account, name = "Hide total balance", description = "Enable hide the total balance from the accounts tab", defaultValue = false From 100259b088095dda73afd6cb417326d76c949f28 Mon Sep 17 00:00:00 2001 From: shamim-emon Date: Thu, 26 Sep 2024 20:01:36 +0600 Subject: [PATCH 3/3] Reviews resolved --- .../src/main/java/com/ivy/features/FeaturesScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt index 3374f0434b..6d2a15f10d 100644 --- a/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt +++ b/screen/features/src/main/java/com/ivy/features/FeaturesScreen.kt @@ -64,7 +64,7 @@ private fun FeaturesUi( content = { innerPadding -> Content( modifier = Modifier.padding(innerPadding), - featureItemViewStates = uiState.featureItemViewStates, + items = uiState.featureItemViewStates, onToggleFeature = { onEvent(FeaturesUiEvent.ToggleFeature(it)) } @@ -105,7 +105,7 @@ private fun Title( @Composable private fun Content( - featureItemViewStates: ImmutableList, + items: ImmutableList, onToggleFeature: (String) -> Unit, modifier: Modifier = Modifier, ) { @@ -117,7 +117,7 @@ private fun Content( ), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(featureItemViewStates) { item -> + items(items) { item -> when (item) { is FeatureItemViewState.FeatureHeaderViewState -> { FeatureSectionDivider(text = item.name)