Skip to content

Commit

Permalink
Merge pull request #226 from lihenggui/select
Browse files Browse the repository at this point in the history
Add animation when selecting components
  • Loading branch information
lihenggui authored Jul 12, 2023
2 parents 2f6c68f + 3bafa85 commit e149d68
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.merxury.blocker.feature.search

import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation.Vertical
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -66,9 +68,8 @@ import com.merxury.blocker.feature.search.component.SelectedAppTopBar
import com.merxury.blocker.feature.search.model.ComponentTabUiState
import com.merxury.blocker.feature.search.model.FilteredComponent
import com.merxury.blocker.feature.search.model.LocalSearchUiState
import com.merxury.blocker.feature.search.model.SearchBoxUiState
import com.merxury.blocker.feature.search.model.SearchUiState
import com.merxury.blocker.feature.search.model.SearchViewModel
import com.merxury.blocker.feature.search.model.SelectUiState
import com.merxury.blocker.feature.search.screen.NoSearchResultScreen
import com.merxury.blocker.feature.search.screen.SearchResultScreen
import com.merxury.blocker.feature.search.screen.SearchingScreen
Expand All @@ -80,15 +81,13 @@ fun SearchRoute(
viewModel: SearchViewModel = hiltViewModel(),
appListViewModel: AppListViewModel = hiltViewModel(),
) {
val searchBoxUiState by viewModel.searchBoxUiState.collectAsStateWithLifecycle()
val localSearchUiState by viewModel.localSearchUiState.collectAsStateWithLifecycle()
val tabState by viewModel.tabState.collectAsStateWithLifecycle()
val appList = appListViewModel.appListFlow.collectAsState()
val selectUiState by viewModel.selectUiState.collectAsStateWithLifecycle()
val selectUiState by viewModel.searchUiState.collectAsStateWithLifecycle()
val errorState by viewModel.errorState.collectAsStateWithLifecycle()

SearchScreen(
searchBoxUiState = searchBoxUiState,
tabState = tabState,
localSearchUiState = localSearchUiState,
switchTab = viewModel::switchTab,
Expand All @@ -101,7 +100,7 @@ fun SearchRoute(
onDeselect = viewModel::deselectItem,
onBlockAll = { viewModel.controlAllComponents(false) },
onEnableAll = { viewModel.controlAllComponents(true) },
selectUiState = selectUiState,
searchUiState = selectUiState,
switchSelectedMode = viewModel::switchSelectedMode,
onSelect = viewModel::selectItem,
navigateToAppDetail = navigateToAppDetail,
Expand All @@ -128,9 +127,8 @@ fun SearchRoute(
fun SearchScreen(
modifier: Modifier = Modifier,
tabState: TabState<SearchScreenTabs>,
searchBoxUiState: SearchBoxUiState,
localSearchUiState: LocalSearchUiState,
selectUiState: SelectUiState,
searchUiState: SearchUiState,
switchTab: (SearchScreenTabs) -> Unit,
onSearchTextChanged: (TextFieldValue) -> Unit,
onClearClick: () -> Unit,
Expand All @@ -154,8 +152,7 @@ fun SearchScreen(
Scaffold(
topBar = {
TopBar(
selectUiState = selectUiState,
searchBoxUiState = searchBoxUiState,
searchUiState = searchUiState,
onSearchTextChanged = onSearchTextChanged,
onClearClick = onClearClick,
onNavigationClick = { switchSelectedMode(false) },
Expand Down Expand Up @@ -188,7 +185,7 @@ fun SearchScreen(
tabState = tabState,
switchTab = switchTab,
localSearchUiState = localSearchUiState,
selectUiState = selectUiState,
searchUiState = searchUiState,
switchSelectedMode = switchSelectedMode,
onSelect = onSelect,
onDeselect = onDeselect,
Expand All @@ -212,38 +209,43 @@ fun SearchScreen(
@Composable
fun TopBar(
modifier: Modifier = Modifier,
selectUiState: SelectUiState,
searchBoxUiState: SearchBoxUiState,
searchUiState: SearchUiState,
onSearchTextChanged: (TextFieldValue) -> Unit,
onClearClick: () -> Unit,
onNavigationClick: () -> Unit,
onSelectAll: () -> Unit,
onBlockAll: () -> Unit,
onEnableAll: () -> Unit,
) {
if (selectUiState.isSelectedMode) {
SelectedAppTopBar(
selectedAppCount = selectUiState.selectedAppList.size,
selectedComponentCount = selectUiState.selectedComponentList.size,
onNavigationClick = onNavigationClick,
onSelectAll = onSelectAll,
onBlockAll = onBlockAll,
onEnableAll = onEnableAll,
)
} else {
SearchBar(
modifier = modifier,
uiState = searchBoxUiState,
onSearchTextChanged = onSearchTextChanged,
onClearClick = onClearClick,
)
Crossfade(
searchUiState.isSelectedMode,
animationSpec = tween(500),
label = "topBar",
) { targetState ->
if (targetState) {
SelectedAppTopBar(
selectedAppCount = searchUiState.selectedAppList.size,
selectedComponentCount = searchUiState.selectedComponentList.size,
onNavigationClick = onNavigationClick,
onSelectAll = onSelectAll,
onBlockAll = onBlockAll,
onEnableAll = onEnableAll,
)
} else {
SearchBar(
modifier = modifier,
keyword = searchUiState.keyword,
onSearchTextChanged = onSearchTextChanged,
onClearClick = onClearClick,
)
}
}
}

@Composable
fun ComponentSearchResultContent(
modifier: Modifier = Modifier,
selectUiState: SelectUiState,
searchUiState: SearchUiState,
componentTabUiState: ComponentTabUiState,
switchSelectedMode: (Boolean) -> Unit,
onSelect: (FilteredComponent) -> Unit,
Expand All @@ -267,15 +269,15 @@ fun ComponentSearchResultContent(
items(componentTabUiState.list, key = { it.app.packageName }) {
FilteredComponentItem(
items = it,
isSelectedMode = selectUiState.isSelectedMode,
isSelectedMode = searchUiState.isSelectedMode,
switchSelectedMode = switchSelectedMode,
onSelect = onSelect,
onDeselect = onDeselect,
onComponentClick = { component ->
onComponentClick(component)
analyticsHelper.logComponentSearchResultClicked()
},
isSelected = selectUiState.selectedAppList.contains(it),
isSelected = searchUiState.selectedAppList.contains(it),
)
}
item {
Expand Down Expand Up @@ -355,7 +357,6 @@ fun RuleSearchResultContent(
@Composable
@Preview
fun SearchScreenEmptyPreview() {
val searchBoxUiState = SearchBoxUiState()
val localSearchUiState = LocalSearchUiState.Loading
val tabState = TabState(
items = listOf(
Expand All @@ -367,7 +368,6 @@ fun SearchScreenEmptyPreview() {
)
BlockerTheme {
SearchScreen(
searchBoxUiState = searchBoxUiState,
localSearchUiState = localSearchUiState,
onSearchTextChanged = {},
onClearClick = {},
Expand All @@ -379,15 +379,14 @@ fun SearchScreenEmptyPreview() {
switchSelectedMode = {},
onSelect = {},
onDeselect = {},
selectUiState = SelectUiState(),
searchUiState = SearchUiState(),
)
}
}

@Composable
@Preview
fun SearchScreenNoResultPreview() {
val searchBoxUiState = SearchBoxUiState()
val localSearchUiState = LocalSearchUiState.Idle
val tabState = TabState(
items = listOf(
Expand All @@ -399,7 +398,6 @@ fun SearchScreenNoResultPreview() {
)
BlockerTheme {
SearchScreen(
searchBoxUiState = searchBoxUiState,
localSearchUiState = localSearchUiState,
onSearchTextChanged = {},
onClearClick = {},
Expand All @@ -411,7 +409,7 @@ fun SearchScreenNoResultPreview() {
switchSelectedMode = {},
onSelect = {},
onDeselect = {},
selectUiState = SelectUiState(),
searchUiState = SearchUiState(),
)
}
}
Expand All @@ -426,7 +424,6 @@ fun SearchScreenPreview() {
isSystem = false,
),
)
val searchBoxUiState = SearchBoxUiState()
val localSearchUiState = LocalSearchUiState.Success(
componentTabUiState = ComponentTabUiState(list = listOf(filterAppItem)),
)
Expand All @@ -440,7 +437,6 @@ fun SearchScreenPreview() {
)
BlockerTheme {
SearchScreen(
searchBoxUiState = searchBoxUiState,
localSearchUiState = localSearchUiState,
onSearchTextChanged = {},
onClearClick = {},
Expand All @@ -452,7 +448,7 @@ fun SearchScreenPreview() {
switchSelectedMode = {},
onSelect = {},
onDeselect = {},
selectUiState = SelectUiState(),
searchUiState = SearchUiState(),
)
}
}
Expand All @@ -467,7 +463,6 @@ fun SearchScreenSelectedPreview() {
isSystem = false,
),
)
val searchBoxUiState = SearchBoxUiState()
val localSearchUiState = LocalSearchUiState.Success(
componentTabUiState = ComponentTabUiState(list = listOf(filterAppItem)),
)
Expand All @@ -481,7 +476,6 @@ fun SearchScreenSelectedPreview() {
)
BlockerTheme {
SearchScreen(
searchBoxUiState = searchBoxUiState,
localSearchUiState = localSearchUiState,
onSearchTextChanged = {},
onClearClick = {},
Expand All @@ -492,7 +486,7 @@ fun SearchScreenSelectedPreview() {
onEnableAll = {},
switchSelectedMode = {},
onSelect = {},
selectUiState = SelectUiState(),
searchUiState = SearchUiState(),
onDeselect = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ package com.merxury.blocker.feature.search.component

import android.content.pm.PackageInfo
import android.content.res.Configuration
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
Expand Down Expand Up @@ -66,16 +71,17 @@ fun FilteredComponentItem(
onDeselect: (FilteredComponent) -> Unit,
onComponentClick: (FilteredComponent) -> Unit,
) {
val color = if (isSelected) {
MaterialTheme.colorScheme.tertiaryContainer
} else {
MaterialTheme.colorScheme.background
}
val shape = if (isSelected) {
RoundedCornerShape(12.dp)
val animatedColor = animateColorAsState(
targetValue = if (isSelected) MaterialTheme.colorScheme.tertiaryContainer else MaterialTheme.colorScheme.background,
animationSpec = tween(500, 0, LinearEasing),
label = "color",
)
val radius = if (isSelected) {
12.dp
} else {
RoundedCornerShape(0.dp)
0.dp
}
val cornerRadius = animateDpAsState(targetValue = radius, label = "shape")
Box(
modifier = modifier,
) {
Expand Down Expand Up @@ -103,8 +109,8 @@ fun FilteredComponentItem(
},
)
.background(
color = color,
shape = shape,
color = animatedColor.value,
shape = RoundedCornerShape(cornerRadius.value),
)
.padding(horizontal = 16.dp, vertical = 8.dp),
) {
Expand All @@ -124,22 +130,28 @@ private fun SelectableAppIcon(
modifier: Modifier = Modifier,
isSelected: Boolean,
) {
if (isSelected) {
Icon(
imageVector = BlockerIcons.Check,
modifier = modifier.size(48.dp),
contentDescription = null,
)
} else {
AsyncImage(
modifier = modifier
.size(48.dp),
model = Builder(LocalContext.current)
.data(info)
.crossfade(true)
.build(),
contentDescription = null,
)
Crossfade(
isSelected,
animationSpec = tween(500),
label = "icon",
) { targetState ->
if (targetState) {
Icon(
imageVector = BlockerIcons.Check,
modifier = modifier.size(48.dp),
contentDescription = null,
)
} else {
AsyncImage(
modifier = modifier
.size(48.dp),
model = Builder(LocalContext.current)
.data(info)
.crossfade(true)
.build(),
contentDescription = null,
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import com.merxury.blocker.core.designsystem.icon.BlockerIcons
import com.merxury.blocker.core.designsystem.theme.BlockerTheme
import com.merxury.blocker.feature.search.R.plurals
import com.merxury.blocker.feature.search.R.string
import com.merxury.blocker.feature.search.model.SearchBoxUiState

@Composable
fun SelectedAppTopBar(
Expand Down Expand Up @@ -125,7 +124,7 @@ fun SelectedAppTopBar(
@Composable
fun SearchBar(
modifier: Modifier = Modifier,
uiState: SearchBoxUiState,
keyword: TextFieldValue,
onSearchTextChanged: (TextFieldValue) -> Unit,
onClearClick: () -> Unit,
) {
Expand All @@ -137,7 +136,7 @@ fun SearchBar(
title = stringResource(id = string.searching),
actions = {
BlockerSearchTextField(
keyword = uiState.keyword,
keyword = keyword,
placeholder = {
Text(
text = stringResource(id = string.search_hint),
Expand Down
Loading

0 comments on commit e149d68

Please sign in to comment.