Add ability to expand pinned tags to multiple lines

This commit is contained in:
MM20
2023-03-11 00:26:02 +01:00
parent eae063879c
commit c37b280d29
8 changed files with 261 additions and 129 deletions
+1
View File
@@ -39,6 +39,7 @@ android {
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.ui.text.ExperimentalTextApi",
"-opt-in=androidx.compose.ui.unit.ExperimentalUnitApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
@@ -0,0 +1,167 @@
package de.mm20.launcher2.ui.common
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.ExpandLess
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
@Composable
fun FavoritesTagSelector(
tags: List<Tag>,
selectedTag: String?,
editButton: Boolean,
reverse: Boolean,
onSelectTag: (String?) -> Unit,
scrollState: ScrollState,
expanded: Boolean,
onExpand: (Boolean) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.padding(
top = if (reverse) 8.dp else 4.dp,
bottom = if (reverse) 4.dp else 8.dp,
end = if (editButton) 8.dp else 0.dp
)
then
if (editButton && expanded) Modifier.height(IntrinsicSize.Min) else Modifier,
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
if (!expanded) {
val canScroll by remember {
derivedStateOf { scrollState.canScrollForward || scrollState.canScrollBackward }
}
Row(
modifier = Modifier
.weight(1f)
.horizontalScroll(scrollState)
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
FilterChip(
modifier = Modifier.padding(start = 8.dp),
selected = selectedTag == tag.tag,
onClick = { onSelectTag(tag.tag) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Tag,
contentDescription = null
)
},
label = { Text(tag.label) }
)
}
if (canScroll) {
IconButton(
onClick = { onExpand(true) }) {
Icon(Icons.Rounded.ExpandMore, null)
}
}
}
} else {
FlowRow(
modifier = Modifier
.weight(1f)
.padding(end = 12.dp, start = 16.dp),
) {
FilterChip(
modifier = Modifier.padding(end = 8.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
FilterChip(
modifier = Modifier.padding(end = 8.dp),
selected = selectedTag == tag.tag,
onClick = { onSelectTag(tag.tag) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Tag,
contentDescription = null
)
},
label = { Text(tag.label) }
)
}
}
}
if (editButton || expanded) {
Column(
modifier = if (expanded && editButton) Modifier.fillMaxHeight() else Modifier,
verticalArrangement = if (expanded && editButton) Arrangement.SpaceBetween else Arrangement.Center,
) {
if (expanded) {
IconButton(onClick = { onExpand(false) }) {
Icon(Icons.Rounded.ExpandLess, null)
}
}
if (editButton) {
val sheetManager = LocalBottomSheetManager.current
SmallFloatingActionButton(
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(),
onClick = { sheetManager.showEditFavoritesSheet() }
) {
Icon(
imageVector = Icons.Rounded.Edit,
contentDescription = null
)
}
}
}
}
}
}
@@ -13,16 +13,17 @@ import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
open class FavoritesVM : ViewModel(), KoinComponent {
abstract class FavoritesVM : ViewModel(), KoinComponent {
private val favoritesRepository: FavoritesRepository by inject()
private val widgetRepository: WidgetRepository by inject()
private val customAttributesRepository: CustomAttributesRepository by inject()
private val dataStore: LauncherDataStore by inject()
internal val dataStore: LauncherDataStore by inject()
val selectedTag = MutableStateFlow<String?>(null)
val showEditButton = dataStore.data.map { it.favorites.editButton }
abstract val tagsExpanded: Flow<Boolean>
val pinnedTags = favoritesRepository.getFavorites(
includeTypes = listOf("tag"),
@@ -88,4 +89,6 @@ open class FavoritesVM : ViewModel(), KoinComponent {
fun selectTag(tag: String?) {
selectedTag.value = tag
}
abstract fun setTagsExpanded(expanded: Boolean)
}
@@ -4,6 +4,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -17,6 +18,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Tag
@@ -24,12 +26,14 @@ import androidx.compose.material.icons.rounded.Work
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -44,6 +48,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.FavoritesTagSelector
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
@@ -108,6 +113,7 @@ fun SearchColumn(
val selectedTag by favoritesVM.selectedTag.collectAsState(null)
val tagsScrollState = rememberScrollState()
val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false)
val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false)
LazyColumn(
state = state,
@@ -136,63 +142,16 @@ fun SearchColumn(
null
} else {
{
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
top = if (reverse) 8.dp else 4.dp,
bottom = if (reverse) 4.dp else 8.dp,
end = if (favoritesEditButton) 8.dp else 0.dp
),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.weight(1f)
.horizontalScroll(tagsScrollState)
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { favoritesVM.selectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in pinnedTags) {
FilterChip(
modifier = Modifier.padding(start = 8.dp),
selected = selectedTag == tag.tag,
onClick = { favoritesVM.selectTag(tag.tag) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Tag,
contentDescription = null
)
},
label = { Text(tag.label) }
)
}
}
if (favoritesEditButton) {
val sheetManager = LocalBottomSheetManager.current
SmallFloatingActionButton(
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(),
onClick = { sheetManager.showEditFavoritesSheet() }
) {
Icon(
imageVector = Icons.Rounded.Edit,
contentDescription = null
)
}
}
}
FavoritesTagSelector(
tags = pinnedTags,
selectedTag = selectedTag,
editButton = favoritesEditButton,
reverse = reverse,
onSelectTag = { favoritesVM.selectTag(it) },
scrollState = tagsScrollState,
expanded = favoritesTagsExpanded,
onExpand = { favoritesVM.setTagsExpanded(it) }
)
}
},
highlightedItem = bestMatch as? SavableSearchable
@@ -1,5 +1,29 @@
package de.mm20.launcher2.ui.launcher.search.favorites
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ui.common.FavoritesVM
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
class SearchFavoritesVM: FavoritesVM()
class SearchFavoritesVM : FavoritesVM() {
override val tagsExpanded: Flow<Boolean> = dataStore.data.map { it.ui.searchTagsMultiline }
.shareIn(viewModelScope, SharingStarted.Lazily)
override fun setTagsExpanded(expanded: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setUi(
it.ui.toBuilder()
.setSearchTagsMultiline(expanded)
.build()
)
.build()
}
}
}
}
@@ -1,36 +1,23 @@
package de.mm20.launcher2.ui.launcher.widgets.favorites
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.FavoritesTagSelector
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
@Composable
fun FavoritesWidget() {
@@ -40,6 +27,8 @@ fun FavoritesWidget() {
val selectedTag by viewModel.selectedTag.collectAsState(null)
val favoritesEditButton by viewModel.showEditButton.collectAsState(false)
val tagsExpanded by viewModel.tagsExpanded.collectAsState(false)
Column {
if (favorites.isNotEmpty()) {
SearchResultGrid(favorites)
@@ -53,60 +42,16 @@ fun FavoritesWidget() {
)
}
if (pinnedTags.isNotEmpty() || favoritesEditButton) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
top = 4.dp,
bottom = 8.dp,
end = if (favoritesEditButton) 8.dp else 0.dp
),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.weight(1f)
.horizontalScroll(rememberScrollState())
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { viewModel.selectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in pinnedTags) {
FilterChip(
modifier = Modifier.padding(start = 8.dp),
selected = selectedTag == tag.tag,
onClick = { viewModel.selectTag(tag.tag) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Tag,
contentDescription = null
)
},
label = { Text(tag.label) }
)
}
}
if (favoritesEditButton) {
val bottomSheetManager = LocalBottomSheetManager.current
SmallFloatingActionButton(
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(),
onClick = { bottomSheetManager.showEditFavoritesSheet() }
) {
Icon(imageVector = Icons.Rounded.Edit, contentDescription = null)
}
}
}
FavoritesTagSelector(
tags = pinnedTags,
selectedTag = selectedTag,
editButton = favoritesEditButton,
reverse = false,
onSelectTag = { viewModel.selectTag(it) },
scrollState = rememberScrollState(),
expanded = tagsExpanded,
onExpand = { viewModel.setTagsExpanded(it) }
)
}
}
}
@@ -1,5 +1,29 @@
package de.mm20.launcher2.ui.launcher.widgets.favorites
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ui.common.FavoritesVM
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
class FavoritesWidgetVM: FavoritesVM()
class FavoritesWidgetVM: FavoritesVM() {
override val tagsExpanded: Flow<Boolean> = dataStore.data.map { it.ui.widgetTagsMultiline }
.shareIn(viewModelScope, SharingStarted.Lazily)
override fun setTagsExpanded(expanded: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setUi(
it.ui.toBuilder()
.setWidgetTagsMultiline(expanded)
.build()
)
.build()
}
}
}
}