1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-09 12:32:30 +01:00

Create draggable functions

This commit is contained in:
Allan Wang 2023-06-21 17:40:06 -07:00
parent 24b96ab536
commit e653e77249
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
4 changed files with 218 additions and 15 deletions

View File

@ -0,0 +1,138 @@
/*
* Copyright 2023 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.compose
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import kotlin.math.roundToInt
/*
* Resources:
*
* https://blog.canopas.com/android-drag-and-drop-ui-element-in-jetpack-compose-14922073b3f1
*/
@Composable
fun DragContainer(modifier: Modifier = Modifier, content: @Composable DragScope.() -> Unit) {
val draggable = remember { Draggable() }
val dragScope = remember(draggable) { DragScopeImpl(draggable) }
Box(modifier = modifier) {
dragScope.content()
DraggingContent(draggable = draggable)
}
}
private class DragScopeImpl(private val draggable: Draggable) : DragScope {
@Composable
override fun DragTarget(
key: String,
content: @Composable BoxScope.(isDragging: Boolean) -> Unit
) {
var isDragging by remember { mutableStateOf(false) }
var positionInWindow by remember { mutableStateOf(Offset.Zero) }
var size by remember { mutableStateOf(IntSize.Zero) }
Box(
modifier =
Modifier.onGloballyPositioned {
positionInWindow = it.positionInWindow()
size = it.size
}
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = {
isDragging = true
draggable.composable = content
draggable.composableSize = size
draggable.dragPosition = positionInWindow
},
onDrag = { _, offset -> draggable.dragPosition += offset },
onDragEnd = {
isDragging = false
draggable.composable = null
},
)
},
) {
if (!isDragging) {
content(false)
}
}
}
}
private fun IntSize.toDpSize(density: Density): DpSize {
return with(density) { DpSize(width.toDp(), height.toDp()) }
}
@Composable
private fun DraggingContent(draggable: Draggable) {
val composable = draggable.composable ?: return
val density = LocalDensity.current
val sizeDp =
remember { derivedStateOf { draggable.composableSize?.toDpSize(density) } }.value ?: return
Box(
modifier = Modifier.size(sizeDp).offset { draggable.dragPosition.toIntOffset() },
) {
composable(true)
}
}
private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())
@LayoutScopeMarker
@Immutable
interface DragScope {
@Composable
fun DragTarget(key: String, content: @Composable BoxScope.(isDragging: Boolean) -> Unit)
}
class Draggable {
var composable by mutableStateOf<(@Composable BoxScope.(isDragging: Boolean) -> Unit)?>(null)
var composableSize by mutableStateOf<IntSize?>(null)
var dragPosition by mutableStateOf(Offset.Zero)
}
internal val LocalDraggable = compositionLocalOf {}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2023 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.ext
import androidx.compose.ui.Modifier
fun Modifier.thenIf(condition: Boolean, action: () -> Modifier): Modifier =
if (condition) then(action()) else this

View File

@ -19,6 +19,7 @@ package com.pitchedapps.frost.main
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.ExperimentalMaterialApi
@ -42,6 +43,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.pitchedapps.frost.compose.webview.FrostWebCompose
import com.pitchedapps.frost.ext.WebTargetId
@ -98,7 +100,13 @@ fun MainBottomBar(
NavigationBar(modifier = modifier) {
items.forEach { item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.title) },
icon = {
Icon(
modifier = Modifier.size(24.dp),
imageVector = item.icon,
contentDescription = item.title,
)
},
selected = selectedTab == item.id,
onClick = { onSelect(item.id) },
)

View File

@ -20,6 +20,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
@ -28,6 +29,8 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -40,8 +43,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pitchedapps.frost.compose.DragContainer
import com.pitchedapps.frost.compose.effects.rememberShakeState
import com.pitchedapps.frost.compose.effects.shake
import com.pitchedapps.frost.ext.thenIf
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.tab
@ -61,7 +66,7 @@ fun TabSelectorScreen(modifier: Modifier = Modifier) {
val unselected = remember(selected) { options.values - selected.toSet() }
TabSelector(
modifier = modifier.statusBarsPadding(),
modifier = modifier,
selected = selected,
unselected = unselected,
onSelect = { selected = it },
@ -76,15 +81,21 @@ fun TabSelector(
unselected: List<TabData>,
onSelect: (List<TabData>) -> Unit
) {
DragContainer(modifier = modifier) {
Column(modifier = Modifier.statusBarsPadding()) {
LazyVerticalGrid(
modifier = modifier,
modifier = Modifier.weight(1f),
columns = GridCells.Fixed(4),
) {
items(unselected, key = { it.key }) {
this@DragContainer.DragTarget(key = it.key) { isDragging ->
val shakeState = rememberShakeState()
TabItem(
modifier =
Modifier.animateItemPlacement().shake(shakeState).clickable {
Modifier.thenIf(!isDragging) { Modifier.animateItemPlacement() }
.shake(shakeState)
.clickable {
shakeState.shake()
// onSelect(listOf(it))
},
@ -94,6 +105,30 @@ fun TabSelector(
}
}
TabBottomBar(modifier = Modifier.navigationBarsPadding(), items = selected)
}
}
}
@Composable
fun TabBottomBar(modifier: Modifier = Modifier, items: List<TabData>) {
NavigationBar(modifier = modifier) {
items.forEach { item ->
NavigationBarItem(
icon = {
Icon(
modifier = Modifier.size(24.dp),
imageVector = item.icon,
contentDescription = item.title,
)
},
selected = false,
onClick = {},
)
}
}
}
@Composable
fun TabItem(
data: TabData,