Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Search Functionality In Category Screen #3499

Merged
merged 13 commits into from
Sep 13, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ import com.ivy.data.model.primitive.IconAsset
import com.ivy.data.model.primitive.NotBlankTrimmedString
import com.ivy.design.l0_system.UI
import com.ivy.design.l0_system.style
import com.ivy.legacy.ui.SearchInput
import com.ivy.legacy.utils.balancePrefix
import com.ivy.legacy.utils.compactBalancePrefix
import com.ivy.legacy.utils.format
import com.ivy.legacy.utils.selectEndTextFieldValue
import com.ivy.navigation.CategoriesScreen
import com.ivy.navigation.TransactionsScreen
import com.ivy.navigation.navigation
Expand Down Expand Up @@ -100,7 +102,10 @@ fun BoxWithConstraintsScope.CategoriesScreen(screen: CategoriesScreen) {

@Composable
private fun BoxWithConstraintsScope.UI(
state: CategoriesScreenState = CategoriesScreenState(compactCategoriesModeEnabled = false),
state: CategoriesScreenState = CategoriesScreenState(
compactCategoriesModeEnabled = false,
showCategorySearchBar = false
),
onEvent: (CategoriesScreenEvent) -> Unit = {}
) {
val nav = navigation()
Expand Down Expand Up @@ -158,6 +163,10 @@ private fun BoxWithConstraintsScope.UI(
Spacer(Modifier.width(24.dp))
}

if (state.showCategorySearchBar) {
Spacer(Modifier.height(16.dp))
SearchField(onSearch = { onEvent(CategoriesScreenEvent.OnSearchQueryUpdate(it)) })
}
Spacer(Modifier.height(16.dp))
}

Expand Down Expand Up @@ -679,11 +688,22 @@ private fun PreviewCategoriesCompactModeEnabled(theme: Theme = Theme.LIGHT) {

@Preview
@Composable
private fun Preview(theme: Theme = Theme.LIGHT, compactModeEnabled: Boolean = false) {
private fun PreviewCategoriesCompactModeEnabledAndSearchBarEnabled(theme: Theme = Theme.LIGHT) {
Preview(theme = theme, compactModeEnabled = true, displaySearchBarEnabled = true)
}

@Preview
@Composable
private fun Preview(
theme: Theme = Theme.LIGHT,
compactModeEnabled: Boolean = false,
displaySearchBarEnabled: Boolean = false
) {
com.ivy.legacy.IvyWalletPreview(theme) {
val state = CategoriesScreenState(
baseCurrency = "BGN",
compactCategoriesModeEnabled = compactModeEnabled,
showCategorySearchBar = displaySearchBarEnabled,
categories = persistentListOf(
CategoryData(
category = Category(
Expand Down Expand Up @@ -752,6 +772,106 @@ private fun Preview(theme: Theme = Theme.LIGHT, compactModeEnabled: Boolean = fa
}
}

@Preview
@Composable
private fun PreviewWithSearchBarEnabled(
theme: Theme = Theme.LIGHT,
compactModeEnabled: Boolean = false,
displaySearchBarEnabled: Boolean = true
) {
com.ivy.legacy.IvyWalletPreview(theme) {
shamim-emon marked this conversation as resolved.
Show resolved Hide resolved
val state = CategoriesScreenState(
baseCurrency = "BGN",
compactCategoriesModeEnabled = compactModeEnabled,
showCategorySearchBar = displaySearchBarEnabled,
categories = persistentListOf(
CategoryData(
category = Category(
id = CategoryId(UUID.randomUUID()),
name = NotBlankTrimmedString.unsafe("Groceries"),
color = ColorInt(Green.toArgb()),
icon = IconAsset.unsafe("groceries"),
orderNum = 0.0,
),
monthlyBalance = 2125.0,
monthlyExpenses = 920.0,
monthlyIncome = 3045.0
),
CategoryData(
category = Category(
id = CategoryId(UUID.randomUUID()),
name = NotBlankTrimmedString.unsafe("Fun"),
color = ColorInt(Orange.toArgb()),
icon = IconAsset.unsafe("game"),
orderNum = 0.0,
),
monthlyBalance = 1200.0,
monthlyExpenses = 750.0,
monthlyIncome = 0.0
),
CategoryData(
category = Category(
id = CategoryId(UUID.randomUUID()),
name = NotBlankTrimmedString.unsafe("Ivy"),
color = ColorInt(IvyDark.toArgb()),
icon = IconAsset.unsafe("star"),
orderNum = 0.0,
),
monthlyBalance = 1200.0,
monthlyExpenses = 0.0,
monthlyIncome = 5000.0
),
CategoryData(
category = Category(
id = CategoryId(UUID.randomUUID()),
name = NotBlankTrimmedString.unsafe("Food"),
color = ColorInt(GreenLight.toArgb()),
icon = IconAsset.unsafe("atom"),
orderNum = 0.0,
),
monthlyBalance = 12125.21,
monthlyExpenses = 1350.50,
monthlyIncome = 8000.48
),
CategoryData(
category = Category(
id = CategoryId(UUID.randomUUID()),
name = NotBlankTrimmedString.unsafe("Shisha"),
color = ColorInt(GreenDark.toArgb()),
icon = IconAsset.unsafe("drink"),
orderNum = 0.0,
),
monthlyBalance = 820.0,
monthlyExpenses = 340.0,
monthlyIncome = 400.0
),

)
)
UI(state = state)
}
}

@Composable
private fun SearchField(
onSearch: (String) -> Unit,
) {
var searchQueryTextFieldValue by remember {
mutableStateOf(selectEndTextFieldValue(""))
}

SearchInput(
searchQueryTextFieldValue = searchQueryTextFieldValue,
hint = "Search categories",
focus = false,
showClearIcon = searchQueryTextFieldValue.text.isNotEmpty(),
onSetSearchQueryTextField = {
searchQueryTextFieldValue = it
onSearch(it.text)
}
)
}

/** For screenshot testing */
@Composable
fun CategoriesScreenUiTest(isDark: Boolean) {
Expand All @@ -762,6 +882,16 @@ fun CategoriesScreenUiTest(isDark: Boolean) {
Preview(theme)
}

/** For screenshot testing */
@Composable
fun CategoriesScreenWithSearchBarUiTest(isDark: Boolean) {
val theme = when (isDark) {
true -> Theme.DARK
false -> Theme.LIGHT
}
Preview(theme = theme, displaySearchBarEnabled = true)
}

/** For screenshot testing */
@Composable
fun CategoriesScreenCompactUiTest(isDark: Boolean) {
Expand All @@ -770,4 +900,14 @@ fun CategoriesScreenCompactUiTest(isDark: Boolean) {
false -> Theme.LIGHT
}
Preview(theme, compactModeEnabled = true)
}

/** For screenshot testing */
@Composable
fun CategoriesScreenWithSearchBarCompactUiTest(isDark: Boolean) {
val theme = when (isDark) {
true -> Theme.DARK
false -> Theme.LIGHT
}
Preview(theme, compactModeEnabled = true, displaySearchBarEnabled = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ sealed interface CategoriesScreenEvent {
data class OnSortOrderModalVisible(val visible: Boolean) : CategoriesScreenEvent
data class OnCategoryModalVisible(val categoryModalData: CategoryModalData?) :
CategoriesScreenEvent
data class OnSearchQueryUpdate(val queryString: String) : CategoriesScreenEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ data class CategoriesScreenState(
val sortOrderItems: ImmutableList<SortOrder> = SortOrder.values().toList().toImmutableList(),
val sortOrder: SortOrder = SortOrder.DEFAULT,
val compactCategoriesModeEnabled: Boolean,
val showCategorySearchBar: Boolean,

)
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import com.ivy.base.legacy.SharedPrefs
import com.ivy.base.legacy.Transaction
import com.ivy.base.time.TimeConverter
import com.ivy.base.time.TimeProvider
import com.ivy.ui.ComposeViewModel
import com.ivy.data.repository.CategoryRepository
import com.ivy.domain.features.Features
import com.ivy.frp.action.thenMap
import com.ivy.frp.thenInvokeAfter
import com.ivy.legacy.data.model.TimePeriod
import com.ivy.legacy.datamodel.Account
import com.ivy.legacy.utils.ioThread
import com.ivy.ui.ComposeViewModel
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.category.LegacyCategoryIncomeWithAccountFiltersAct
import com.ivy.wallet.domain.action.settings.BaseCurrencyAct
Expand Down Expand Up @@ -55,6 +55,8 @@ class CategoriesViewModel @Inject constructor(
private val baseCurrency = mutableStateOf("")
private val categories =
mutableStateOf<ImmutableList<CategoryData>>(persistentListOf<CategoryData>())
private val allCategories =
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
mutableStateOf<ImmutableList<CategoryData>>(persistentListOf<CategoryData>())
private val reorderModalVisible = mutableStateOf(false)
private val categoryModalData = mutableStateOf<CategoryModalData?>(null)
private val sortModalVisible = mutableStateOf(false)
Expand All @@ -74,6 +76,7 @@ class CategoriesViewModel @Inject constructor(
sortOrder = getSortOrder(),
sortModalVisible = getSortModalVisible(),
compactCategoriesModeEnabled = getCompactCategoriesMode(),
showCategorySearchBar = getShowCategorySearchBar()
)
}

Expand All @@ -82,6 +85,11 @@ class CategoriesViewModel @Inject constructor(
return features.compactCategoriesMode.asEnabledState()
}

@Composable
private fun getShowCategorySearchBar(): Boolean {
return features.showCategorySearchBar.asEnabledState()
}

@Composable
private fun getBaseCurrency(): String {
return baseCurrency.value
Expand Down Expand Up @@ -126,7 +134,11 @@ class CategoriesViewModel @Inject constructor(
ioThread {
val range = TimePeriod.currentMonth(
startDayOfMonth = ivyContext.startDayOfMonth
).toRange(ivyContext.startDayOfMonth, timeConverter, timeProvider) // this must be monthly
).toRange(
ivyContext.startDayOfMonth,
timeConverter,
timeProvider
) // this must be monthly

allAccounts = accountsAct(Unit)
baseCurrency.value = baseCurrencyAct(Unit)
Expand All @@ -135,7 +147,7 @@ class CategoriesViewModel @Inject constructor(
TrnsWithRangeAndAccFiltersAct.Input(
range = range,
accountIdFilterSet = suspend { allAccounts } thenMap { it.id }
thenInvokeAfter { it.toHashSet() }
thenInvokeAfter { it.toHashSet() }
)
)

Expand Down Expand Up @@ -171,9 +183,21 @@ class CategoriesViewModel @Inject constructor(
}

val sortedList = sortList(categories, sortOrder.value).toImmutableList()
this.allCategories.value = sortedList
this.categories.value = this.allCategories.value
}
}

this.categories.value = sortedList
private fun filterCategories(queryString: String) {
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
var unsortedList: List<CategoryData>
if (queryString.isNotEmpty()) {
unsortedList = this.allCategories.value.filter {
it.category.name.value.toLowerCase().contains(queryString.toLowerCase().trim())
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
unsortedList = this.allCategories.value
}
this.categories.value = sortList(unsortedList, sortOrder.value).toImmutableList()
}

private suspend fun reorder(
Expand Down Expand Up @@ -244,6 +268,8 @@ class CategoriesViewModel @Inject constructor(
is CategoriesScreenEvent.OnCategoryModalVisible -> {
categoryModalData.value = event.categoryModalData
}

is CategoriesScreenEvent.OnSearchQueryUpdate -> filterCategories(event.queryString)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,24 @@ class CategoriesScreenPaparazziTest(
}
}

@Test
fun `snapshot Categories nonCompact Screen with search bar`() {
snapshot(theme) {
CategoriesScreenWithSearchBarUiTest(theme == PaparazziTheme.Dark)
}
}

@Test
fun `snapshot Categories compact Screen`() {
snapshot(theme) {
CategoriesScreenCompactUiTest(theme == PaparazziTheme.Dark)
}
}
}

@Test
fun `snapshot Categories compact Screen with search bar`() {
snapshot(theme) {
CategoriesScreenWithSearchBarCompactUiTest(theme == PaparazziTheme.Dark)
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Features {
val compactAccountsMode: BoolFeature
val compactCategoriesMode: BoolFeature
val showTitleSuggestions: BoolFeature
val showCategorySearchBar: BoolFeature
val hideTotalBalance: BoolFeature

val allFeatures: List<BoolFeature>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class IvyFeatures @Inject constructor() : Features {
defaultValue = true
)

override val showCategorySearchBar = BoolFeature(
key = "show_category_search_bar",
name = "Show category search bar",
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
description = "Show search bar in category screen",
defaultValue = false
ILIYANGERMANOV marked this conversation as resolved.
Show resolved Hide resolved
)

override val hideTotalBalance = BoolFeature(
key = "hide_total_balance",
name = "Hide total balance",
Expand All @@ -45,6 +52,7 @@ class IvyFeatures @Inject constructor() : Features {
compactAccountsMode,
compactCategoriesMode,
showTitleSuggestions,
showCategorySearchBar,
hideTotalBalance
)
}
19 changes: 11 additions & 8 deletions temp/legacy-code/src/main/java/com/ivy/legacy/ui/SearchInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fun SearchInput(
searchQueryTextFieldValue: TextFieldValue,
hint: String,
focus: Boolean = true,
showClearIcon: Boolean = true,
onSetSearchQueryTextField: (TextFieldValue) -> Unit
) {
Row(
Expand Down Expand Up @@ -61,13 +62,15 @@ fun SearchInput(
}
}

IvyIcon(
modifier = Modifier
.weight(1f)
.clickable {
onSetSearchQueryTextField(selectEndTextFieldValue(""))
},
icon = R.drawable.ic_outline_clear_24
)
if (showClearIcon) {
IvyIcon(
modifier = Modifier
.weight(1f)
.clickable {
onSetSearchQueryTextField(selectEndTextFieldValue(""))
},
icon = R.drawable.ic_outline_clear_24
)
}
}
}