Rebase interactive is shown on top of log instead of taking all the space

This commit is contained in:
Abdelilah El Aissaoui 2023-09-07 14:04:26 +02:00
parent 117c5f4fd2
commit 1778f69622
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
4 changed files with 240 additions and 30 deletions

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

@ -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)
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()
},
onDragStart = { offset -> dragDropState.onDragStart(offset) },
onDragEnd = { dragDropState.onDragInterrupted() },
onDragCancel = { 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)
}
}