From 60990e767c3eaaf5f8958085cbcb9f96bb8bf186 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 24 Sep 2022 23:01:04 +0200 Subject: [PATCH] Drop items on tags in edit favorites sheet to tag them --- .../customattrs/CustomAttributesRepository.kt | 8 +++ .../mm20/launcher2/database/CustomAttrsDao.kt | 13 ++++ .../ui/launcher/helper/DragAndDropGrid.kt | 6 +- .../ui/launcher/modals/EditFavoritesSheet.kt | 70 +++++++++++++++++-- .../launcher/modals/EditFavoritesSheetVM.kt | 10 +++ 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt index d8aaea76..519546e4 100644 --- a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt +++ b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt @@ -32,6 +32,7 @@ interface CustomAttributesRepository { suspend fun getAllTags(startsWith: String? = null): List fun getItemsForTag(tag: String): Flow> + fun addTag(item: Searchable, tag: String) } internal class CustomAttributesRepositoryImpl( @@ -122,6 +123,13 @@ internal class CustomAttributesRepositoryImpl( } } + override fun addTag(item: Searchable, tag: String) { + val dao = appDatabase.customAttrsDao() + scope.launch { + dao.addTag(item.key, tag) + } + } + override suspend fun search(query: String): Flow> { if (query.isBlank()) { return flow { diff --git a/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt b/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt index d592af59..c39799dd 100644 --- a/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt +++ b/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt @@ -41,4 +41,17 @@ interface CustomAttrsDao { @Query("SELECT key FROM CustomAttributes WHERE type = 'tag' AND value = :tag") fun getItemsWithTag(tag: String): Flow> + + @Transaction + suspend fun addTag(key: String, tag: String) { + removeTag(key, tag) + insertTag(key, tag) + } + + @Query("DELETE FROM CustomAttributes WHERE type = 'tag' AND key = :key AND value = :tag") + suspend fun removeTag(key: String, tag: String) + + @Query("INSERT INTO CustomAttributes (key, value, type) VALUES (:key, :tag, 'tag')") + suspend fun insertTag(key: String, tag: String) + } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/helper/DragAndDropGrid.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/helper/DragAndDropGrid.kt index c93de836..41c59c62 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/helper/DragAndDropGrid.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/helper/DragAndDropGrid.kt @@ -31,7 +31,7 @@ import kotlin.coroutines.coroutineContext fun rememberLazyDragAndDropGridState( gridState: LazyGridState = rememberLazyGridState(), onDragStart: (item: LazyGridItemInfo) -> Boolean = { true }, - onDrag: (item: LazyGridItemInfo, offset: Offset) -> Unit = {_, _ ->}, + onDrag: (item: LazyGridItemInfo, offset: Offset, position: Offset) -> Unit = {_, _, _ ->}, onDragEnd: (item: LazyGridItemInfo) -> Unit = {}, onDragCancel: (item: LazyGridItemInfo) -> Unit = {}, onItemMove: (from: LazyGridItemInfo, to: LazyGridItemInfo) -> Unit @@ -51,7 +51,7 @@ fun rememberLazyDragAndDropGridState( data class LazyDragAndDropGridState( val gridState: LazyGridState, val onDragStart: (item: LazyGridItemInfo) -> Boolean = { true }, - val onDrag: (item: LazyGridItemInfo, offset: Offset) -> Unit = {_, _ ->}, + val onDrag: (item: LazyGridItemInfo, offset: Offset, position: Offset) -> Unit = {_, _, _ ->}, val onDragEnd: (item: LazyGridItemInfo) -> Unit = {}, val onDragCancel: (item: LazyGridItemInfo) -> Unit = {}, val onItemMove: (from: LazyGridItemInfo, to: LazyGridItemInfo) -> Unit @@ -284,7 +284,7 @@ fun Modifier.dragAndDrop( state.endScrolling() } - state.draggedItemOffset?.let { state.onDrag(draggedItem, it) } + state.draggedItemOffset?.let { state.onDrag(draggedItem, it, absPosition) } } }, onDragCancel = { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt index 5781b908..59e5c00c 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt @@ -9,11 +9,17 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* import androidx.compose.material3.* @@ -22,9 +28,11 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -32,6 +40,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toOffset +import androidx.compose.ui.unit.toSize import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.icons.LauncherIcon @@ -43,6 +53,8 @@ import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.launcher.helper.* import de.mm20.launcher2.ui.locals.LocalGridColumns +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.isActive import kotlin.math.roundToInt @Composable @@ -119,19 +131,55 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) { val contextMenuCloseDistance = 8.dp.toPixels() + var draggedItemKey by remember { mutableStateOf(null) } + var hoveredTag by remember { mutableStateOf(null) } + + val gridState = rememberLazyGridState() + val tagsListState = rememberLazyListState() + val tagsTitleSize = 48.dp.toPixels() val state = rememberLazyDragAndDropGridState( + gridState = gridState, onDragStart = { val item = items.getOrNull(it.index) if (item !is FavoritesSheetGridItem.Favorite) return@rememberLazyDragAndDropGridState false + draggedItemKey = item.item.key contextMenuItemKey = item.item.key true }, - onDrag = { _, offset -> + onDrag = { item, offset, position -> if (offset.getDistanceSquared() > contextMenuCloseDistance) { contextMenuItemKey = null } + val draggedCenter = Rect(position, item.size.toSize()).center + val hoveredItem = gridState.layoutInfo.visibleItemsInfo.find { + Rect( + it.offset.toOffset(), + it.size.toSize() + ).contains(draggedCenter) + } + if (hoveredItem != null + && items[hoveredItem.index] is FavoritesSheetGridItem.Tags + && hoveredItem.offset.y + tagsTitleSize < position.y + ) { + val scroll = tagsListState.layoutInfo.viewportStartOffset + val tag = tagsListState.layoutInfo.visibleItemsInfo.find { + position.x + scroll > it.offset && position.x + scroll < it.offset + it.size + } + hoveredTag = tag?.index?.let { pinnedTags[it].tag } + } else { + hoveredTag = null + } + }, + onDragEnd = { + viewModel.addTag(draggedItemKey, hoveredTag) + draggedItemKey = null + hoveredTag = null + }, + onDragCancel = { + draggedItemKey = null + hoveredTag = null } ) { from, to -> viewModel.moveItem(from, to) @@ -428,10 +476,15 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) { } if (pinnedTags.isNotEmpty()) { - val rowState = rememberLazyDragAndDropListState { from, to -> + val rowState = rememberLazyDragAndDropListState( + listState = tagsListState, + ) { from, to -> viewModel.moveTag(from, to) } - LazyDragAndDropRow(state = rowState) { + LazyDragAndDropRow( + modifier = Modifier.fillMaxWidth(), + state = rowState + ) { items( pinnedTags, key = { it.key } @@ -439,8 +492,15 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) { DraggableItem(state = rowState, key = tag.key) { dragged -> FilterChip( - modifier = Modifier.padding(end = 12.dp), - selected = false, + modifier = Modifier + .padding(end = 12.dp) + .pointerInput(null) { + val coroutineContext = + currentCoroutineContext() + + } + , + selected = tag.tag == hoveredTag, onClick = {}, label = { Text(tag.label) }, leadingIcon = { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt index 6d3fd01f..ebaf9d33 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt @@ -296,4 +296,14 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { save() } + fun addTag(key: String?, tag: String?) { + val gridItems = gridItems.value?.toMutableList() ?: return + if (key == null || tag == null) return + val item = + gridItems.find { it is FavoritesSheetGridItem.Favorite && it.item.key == key } as FavoritesSheetGridItem.Favorite? + if (item != null) { + customAttributesRepository.addTag(item.item, tag) + } + } + } \ No newline at end of file