Rebase interactive is shown on top of log instead of taking all the space
This commit is contained in:
parent
117c5f4fd2
commit
1778f69622
@ -1,9 +1,12 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -18,7 +21,11 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.jetpackduba.gitnuro.AppIcons
|
||||
import com.jetpackduba.gitnuro.theme.backgroundSelected
|
||||
import com.jetpackduba.gitnuro.ui.components.*
|
||||
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.rememberVerticalDragDropState
|
||||
import com.jetpackduba.gitnuro.viewmodels.RebaseAction
|
||||
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
|
||||
@ -64,6 +71,7 @@ fun RebaseInteractive(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RebaseStateLoaded(
|
||||
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
||||
@ -84,7 +92,10 @@ fun RebaseStateLoaded(
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
|
||||
ScrollableLazyColumn(modifier = Modifier.weight(1f)) {
|
||||
ScrollableLazyColumn(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
items(stepsList) { rebaseTodoLine ->
|
||||
RebaseCommit(
|
||||
rebaseLine = rebaseTodoLine,
|
||||
|
@ -255,8 +255,6 @@ fun MainContentView(
|
||||
) {
|
||||
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
|
||||
|
||||
println("Rebase interactive state is $rebaseInteractiveState")
|
||||
|
||||
HorizontalSplitPane(
|
||||
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
||||
) {
|
||||
@ -282,7 +280,7 @@ fun MainContentView(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
|
||||
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction && diffSelected == null) {
|
||||
RebaseInteractive()
|
||||
} else if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
||||
Blame(
|
||||
|
@ -27,9 +27,9 @@ import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||
import com.jetpackduba.gitnuro.extensions.onMiddleMouseButtonClick
|
||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.DraggableItem
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.dragContainer
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.rememberDragDropState
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.HorizontalDraggableItem
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.horizontalDragContainer
|
||||
import com.jetpackduba.gitnuro.ui.drag_sorting.rememberHorizontalDragDropState
|
||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
|
||||
import kotlinx.coroutines.delay
|
||||
@ -88,7 +88,7 @@ fun RepositoriesTabPanel(
|
||||
}
|
||||
|
||||
|
||||
val dragDropState = rememberDragDropState(stateHorizontal) { fromIndex, toIndex ->
|
||||
val dragDropState = rememberHorizontalDragDropState(stateHorizontal) { fromIndex, toIndex ->
|
||||
onMoveTab(fromIndex, toIndex)
|
||||
}
|
||||
|
||||
@ -97,14 +97,19 @@ fun RepositoriesTabPanel(
|
||||
modifier = Modifier
|
||||
.height(tabsHeight)
|
||||
.weight(1f, false)
|
||||
.dragContainer(dragDropState),
|
||||
.horizontalDragContainer(dragDropState),
|
||||
state = stateHorizontal,
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = tabs,
|
||||
key = { _, tab -> tab.tabViewModel }
|
||||
) { index, tab ->
|
||||
DraggableItem(dragDropState, index) { _ ->
|
||||
HorizontalDraggableItem(dragDropState, index) { isDragged ->
|
||||
LaunchedEffect(isDragged) {
|
||||
if (isDragged) {
|
||||
onTabSelected(tab)
|
||||
}
|
||||
}
|
||||
Tooltip(tab.path) {
|
||||
Tab(
|
||||
modifier = Modifier,
|
||||
|
@ -4,7 +4,9 @@ import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@ -15,7 +17,6 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@ -23,14 +24,14 @@ import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Composable
|
||||
fun rememberDragDropState(
|
||||
fun rememberHorizontalDragDropState(
|
||||
lazyListState: LazyListState,
|
||||
@Suppress("PrimitiveInLambda")
|
||||
onMove: (Int, Int) -> Unit
|
||||
): DragDropState {
|
||||
): HorizontalDragDropState {
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = remember(lazyListState) {
|
||||
DragDropState(
|
||||
HorizontalDragDropState(
|
||||
state = lazyListState,
|
||||
onMove = onMove,
|
||||
scope = scope
|
||||
@ -45,7 +46,30 @@ fun rememberDragDropState(
|
||||
return state
|
||||
}
|
||||
|
||||
class DragDropState internal constructor(
|
||||
@Composable
|
||||
fun rememberVerticalDragDropState(
|
||||
lazyListState: LazyListState,
|
||||
@Suppress("PrimitiveInLambda")
|
||||
onMove: (Int, Int) -> Unit
|
||||
): VerticalDragDropState {
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = remember(lazyListState) {
|
||||
VerticalDragDropState(
|
||||
state = lazyListState,
|
||||
onMove = onMove,
|
||||
scope = scope
|
||||
)
|
||||
}
|
||||
LaunchedEffect(state) {
|
||||
while (true) {
|
||||
val diff = state.scrollChannel.receive()
|
||||
lazyListState.scrollBy(diff)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
class HorizontalDragDropState internal constructor(
|
||||
private val state: LazyListState,
|
||||
private val scope: CoroutineScope,
|
||||
@Suppress("PrimitiveInLambda")
|
||||
@ -116,6 +140,7 @@ class DragDropState internal constructor(
|
||||
middleOffset.toInt() in item.offset..item.offsetEnd &&
|
||||
draggingItem.index != item.index
|
||||
}
|
||||
|
||||
if (targetItem != null) {
|
||||
val scrollToIndex = if (targetItem.index == state.firstVisibleItemIndex) {
|
||||
draggingItem.index
|
||||
@ -138,8 +163,10 @@ class DragDropState internal constructor(
|
||||
val overscroll = when {
|
||||
draggingItemDraggedDelta > 0 ->
|
||||
(endOffset - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
|
||||
|
||||
draggingItemDraggedDelta < 0 ->
|
||||
(startOffset - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
|
||||
|
||||
else -> 0f
|
||||
}
|
||||
if (overscroll != 0f) {
|
||||
@ -152,29 +179,168 @@ class DragDropState internal constructor(
|
||||
get() = this.offset + this.size
|
||||
}
|
||||
|
||||
fun Modifier.dragContainer(dragDropState: DragDropState): Modifier {
|
||||
return pointerInput(dragDropState) {
|
||||
detectDragGestures(
|
||||
onDrag = { change, offset ->
|
||||
change.consume()
|
||||
dragDropState.onDrag(offset = offset)
|
||||
},
|
||||
onDragStart = { offset -> dragDropState.onDragStart(offset) },
|
||||
onDragEnd = { dragDropState.onDragInterrupted() },
|
||||
onDragCancel = { dragDropState.onDragInterrupted() }
|
||||
)
|
||||
class VerticalDragDropState internal constructor(
|
||||
private val state: LazyListState,
|
||||
private val scope: CoroutineScope,
|
||||
@Suppress("PrimitiveInLambda")
|
||||
private val onMove: (Int, Int) -> Unit
|
||||
) {
|
||||
var draggingItemIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
internal val scrollChannel = Channel<Float>()
|
||||
|
||||
private var draggingItemDraggedDelta by mutableStateOf(0f)
|
||||
private var draggingItemInitialOffset by mutableStateOf(0)
|
||||
internal val draggingItemOffset: Float
|
||||
get() = draggingItemLayoutInfo?.let { item ->
|
||||
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset
|
||||
} ?: 0f
|
||||
|
||||
private val draggingItemLayoutInfo: LazyListItemInfo?
|
||||
get() = state.layoutInfo.visibleItemsInfo
|
||||
.firstOrNull { it.index == draggingItemIndex }
|
||||
|
||||
internal var previousIndexOfDraggedItem by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
internal var previousItemOffset = Animatable(0f)
|
||||
private set
|
||||
|
||||
internal fun onDragStart(offset: Offset) {
|
||||
state.layoutInfo.visibleItemsInfo
|
||||
.firstOrNull { item ->
|
||||
offset.y.toInt() in item.offset..(item.offset + item.size)
|
||||
}?.also {
|
||||
draggingItemIndex = it.index
|
||||
draggingItemInitialOffset = it.offset
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onDragInterrupted() {
|
||||
if (draggingItemIndex != null) {
|
||||
previousIndexOfDraggedItem = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
previousItemOffset.snapTo(startOffset)
|
||||
previousItemOffset.animateTo(
|
||||
0f,
|
||||
spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = 1f
|
||||
)
|
||||
)
|
||||
previousIndexOfDraggedItem = null
|
||||
}
|
||||
}
|
||||
draggingItemDraggedDelta = 0f
|
||||
draggingItemIndex = null
|
||||
draggingItemInitialOffset = 0
|
||||
}
|
||||
|
||||
internal fun onDrag(offset: Offset) {
|
||||
draggingItemDraggedDelta += offset.y
|
||||
|
||||
val draggingItem = draggingItemLayoutInfo ?: return
|
||||
val startOffset = draggingItem.offset + draggingItemOffset
|
||||
val endOffset = startOffset + draggingItem.size
|
||||
val middleOffset = startOffset + (endOffset - startOffset) / 2f
|
||||
println("Middle offset is $middleOffset")
|
||||
|
||||
val targetItem = state.layoutInfo.visibleItemsInfo.find { item ->
|
||||
middleOffset.toInt() in item.offset..item.offsetEnd &&
|
||||
draggingItem.index != item.index
|
||||
}
|
||||
|
||||
if (targetItem != null) {
|
||||
val scrollToIndex = if (targetItem.index == state.firstVisibleItemIndex) {
|
||||
draggingItem.index
|
||||
} else if (draggingItem.index == state.firstVisibleItemIndex) {
|
||||
targetItem.index
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (scrollToIndex != null) {
|
||||
scope.launch {
|
||||
// this is needed to neutralize automatic keeping the first item first.
|
||||
state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
|
||||
onMove.invoke(draggingItem.index, targetItem.index)
|
||||
}
|
||||
} else {
|
||||
onMove.invoke(draggingItem.index, targetItem.index)
|
||||
}
|
||||
draggingItemIndex = targetItem.index
|
||||
} else {
|
||||
val overscroll = when {
|
||||
draggingItemDraggedDelta > 0 ->
|
||||
(endOffset - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
|
||||
|
||||
draggingItemDraggedDelta < 0 ->
|
||||
(startOffset - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
|
||||
|
||||
else -> 0f
|
||||
}
|
||||
if (overscroll != 0f) {
|
||||
scrollChannel.trySend(overscroll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val LazyListItemInfo.offsetEnd: Int
|
||||
get() = this.offset + this.size
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.horizontalDragContainer(dragDropState: HorizontalDragDropState): Modifier {
|
||||
val state = rememberDraggableState {
|
||||
println("Dragging horizontally $it")
|
||||
dragDropState.onDrag(Offset(it, 0f))
|
||||
}
|
||||
|
||||
return this.draggable(
|
||||
state = state,
|
||||
orientation = Orientation.Horizontal,
|
||||
startDragImmediately = false,
|
||||
onDragStarted = {
|
||||
dragDropState.onDragStart(it)
|
||||
},
|
||||
onDragStopped = {
|
||||
println("On drag stopped")
|
||||
dragDropState.onDragInterrupted()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.verticalDragContainer(dragDropState: VerticalDragDropState): Modifier {
|
||||
val state = rememberDraggableState {
|
||||
println("Dragging vertically $it")
|
||||
dragDropState.onDrag(Offset(0f, it))
|
||||
}
|
||||
|
||||
return this.draggable(
|
||||
state = state,
|
||||
orientation = Orientation.Vertical,
|
||||
startDragImmediately = false,
|
||||
onDragStarted = {
|
||||
dragDropState.onDragStart(it)
|
||||
},
|
||||
onDragStopped = {
|
||||
println("On drag stopped")
|
||||
dragDropState.onDragInterrupted()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun LazyItemScope.DraggableItem(
|
||||
dragDropState: DragDropState,
|
||||
fun LazyItemScope.HorizontalDraggableItem(
|
||||
dragDropState: HorizontalDragDropState,
|
||||
index: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.(isDragging: Boolean) -> Unit
|
||||
) {
|
||||
val dragging = index == dragDropState.draggingItemIndex
|
||||
|
||||
val draggingModifier = if (dragging) {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
@ -193,3 +359,33 @@ fun LazyItemScope.DraggableItem(
|
||||
content(dragging)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun LazyItemScope.VerticalDraggableItem(
|
||||
dragDropState: VerticalDragDropState,
|
||||
index: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.(isDragging: Boolean) -> Unit
|
||||
) {
|
||||
val dragging = index == dragDropState.draggingItemIndex
|
||||
|
||||
val draggingModifier = if (dragging) {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer {
|
||||
translationY = dragDropState.draggingItemOffset
|
||||
}
|
||||
} else if (index == dragDropState.previousIndexOfDraggedItem) {
|
||||
Modifier.zIndex(1f)
|
||||
.graphicsLayer {
|
||||
translationY = dragDropState.previousItemOffset.value
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Column(modifier = modifier.then(draggingModifier)) {
|
||||
content(dragging)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user