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:
parent
24b96ab536
commit
e653e77249
@ -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 {}
|
@ -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
|
@ -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) },
|
||||
)
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user