Giter VIP home page Giter VIP logo

composereorderable's Introduction

Compose LazyList/Grid reorder

Latest release

A Jetpack Compose (Android + Desktop) modifier enabling reordering by drag and drop in a LazyList and LazyGrid.

Sample

Download

dependencies {
    implementation("org.burnoutcrew.composereorderable:reorderable:<latest_version>")
}

How to use

  • Create a reorderable state by rememberReorderableLazyListState for LazyList or rememberReorderableLazyGridState for LazyGrid
  • Add the reorderable(state) modifier to your list/grid
  • Inside the list/grid itemscope create a ReorderableItem(state, key = ) for a keyed lists or ReorderableItem(state, index = ) for a indexed only list. (Animated items only work with keyed lists)
  • Apply the detectReorderAfterLongPress(state) or detectReorder(state) modifier to the list. If only a drag handle is needed apply the detect modifier to any child composable inside the item layout.

ReorderableItem provides the item dragging state, use this to apply elevation , scale etc.

@Composable
fun VerticalReorderList() {
    val data = remember { mutableStateOf(List(100) { "Item $it" }) }
    val state = rememberReorderableLazyListState(onMove = { from, to ->
        data.value = data.value.toMutableList().apply {
            add(to.index, removeAt(from.index))
        }
    })
    LazyColumn(
        state = state.listState,
        modifier = Modifier
        .reorderable(state)
        .detectReorderAfterLongPress(state)
    ) {
        items(data.value, { it }) { item ->
            ReorderableItem(state, key = item) { isDragging ->
                val elevation = animateDpAsState(if (isDragging) 16.dp else 0.dp)
                Column(
                    modifier = Modifier
                        .shadow(elevation.value)
                        .background(MaterialTheme.colors.surface)
                ) {
                    Text(item)
                }
            }
        }
    }
}

The item placement and drag cancelled animation can be changed or disabled by dragCancelledAnimation and defaultDraggingModifier

@Composable
fun VerticalReorderGrid() {
    val data = remember { mutableStateOf(List(100) { "Item $it" }) }
    val state = rememberReorderableLazyGridState(dragCancelledAnimation = NoDragCancelledAnimation(),
        onMove = { from, to ->
            data.value = data.value.toMutableList().apply {
                add(to.index, removeAt(from.index))
            }
        })
    LazyVerticalGrid(
        columns = GridCells.Fixed(4),
        state = state.gridState,
        modifier = Modifier.reorderable(state)
    ) {
        items(data.value, { it }) { item ->
            ReorderableItem(state, key = item, defaultDraggingModifier = Modifier) { isDragging ->
                Box(
                    modifier = Modifier
                        .aspectRatio(1f)
                        .background(MaterialTheme.colors.surface)
                ) {
                    Text(text = item,
                         modifier = Modifier.detectReorderAfterLongPress(state)
                    )
                }
            }
        }
    }
}

Check out the sample app for different implementation samples.

Notes

It's a known issue that the first visible item does not animate.

License

Copyright 2022 André Claßen

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

composereorderable's People

Contributors

aclassen avatar bierdav avatar dependabot[bot] avatar juby210 avatar ofalvai avatar veselyjan92 avatar wakaztahir avatar warting avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

composereorderable's Issues

LazyVerticalGrid - ReorderableItem only animates vertically

Hi. You have a great library, but I ran into a problem. When trying to drag an element in a LazyVerticalGrid, the animation only happens vertically and not horizontally. However, if you drag an element horizontally, it will change its position, but without animation. It feels like each element is in a column and cannot fly freely. Here is a sample code.

    val data = remember { mutableStateOf(List(100) { "Item $it" }) }
    val state = rememberReorderableLazyGridState(onMove = { from, to ->
        data.value = data.value.toMutableList().apply {
            add(to.index, removeAt(from.index))
        }
    }, canDragOver = {true})
    LazyVerticalGrid(
        columns = Fixed(4),
        state = state.gridState,
        contentPadding = PaddingValues(horizontal = 8.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp),
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        modifier = Modifier
            .reorderable(state)
            .detectReorderAfterLongPress(state)
    ) {
        items(data.value, { it }) { item ->
            ReorderableItem(state, item, defaultDraggingModifier = Modifier.animateItemPlacement()) { isDragging ->
                val elevation = animateDpAsState(if (isDragging) 8.dp else 0.dp)
                Box(
                    contentAlignment = Alignment.Center,
                    modifier = Modifier
                        .shadow(elevation.value)
                        .aspectRatio(1f)
                        .background(MaterialTheme.colors.primary)
                ) {
                    Text(item)
                }
            }
        }
    }

My library versions:

  • kotlin_version = '1.7.20'
  • compose_version = '1.3.0-rc01'
  • kotlinCompilerExtensionVersion = '1.3.2'
  • org.burnoutcrew.composereorderable:reorderable: 0.9.2

Item with different height leaves a blank space when reordered.

It is not happening every time but most of the time. After scrolling it leaves a gap of the item height. Once you scroll a little bit item appears again. My LazyColumn is under accompanist's swipe refresh layout. I am receiving data to through Flow and data can change any moment of the time as it is synced through realm.

Support for Compose 1.2

Hello, I was wondering when a new update will be released that supports Compose 1.2 and the stable LazyGrids?

Overloading of ReorderableState#offsetOf conflicts with Int as key in LazyColumn

If you use an Int as the key in the lazy column items call, e. g.: items(items, key = { it.id }) (with id of type Int), and then call state.offsetOf(item.id)), the overloaded method of ReorderableState#offsetOf(index: Int) is called, which is not what you want. It would be helpful to rename this method to something like offsetOfIndex().

Cheers

Jetpack Compose For Web Support

There's Already #200

Web Support can be added just as easily since compose with skia support for web has launched

org.jetbrains.compose.experimental.jscanvas.enabled=true

and you can add

    js(IR) {
        browser()
        binaries.executable()
    }

inside the kotlin block in library;s build.gradle.kts

Release environment compilation error - missing dependency

If I try to compile the library with the release option. I get the following error. Are we missing release dependency? Or is the example is meant for dev releases?

dependencies {
    implementation("org.burnoutcrew.composereorderable:reorderable:0.6.1")
}

I ended up using:

implementation("org.burnoutcrew.composereorderable:reorderable-desktop:0.6.1")
* What went wrong:
Execution failed for task ':app:checkReleaseAarMetadata'.
> Could not resolve all files for configuration ':app:releaseRuntimeClasspath'.
   > Could not find org.burnoutcrew.composereorderable:reorderable-android:0.6.1.
     Searched in the following locations:
       - https://dl.google.com/dl/android/maven2/org/burnoutcrew/composereorderable/reorderable-android/0.6.1/reorderable-android-0.6.1.pom
       - https://repo.maven.apache.org/maven2/org/burnoutcrew/composereorderable/reorderable-android/0.6.1/reorderable-android-0.6.1.pom
       - https://jitpack.io/org/burnoutcrew/composereorderable/reorderable-android/0.6.1/reorderable-android-0.6.1.pom
     Required by:
         project :app > org.burnoutcrew.composereorderable:reorderable:0.6.1

Adding support for Handles

Hello,
I wanted to bring in an idea. Add support for a composable to function as drag handle. So don't have to wait for the long press duration and can immediately start reordering.

Expected Behavior:

  • If you long press the item nothing should happen
  • If you press down the handle composable the dragging should start.
  • As the user releases the handle the dragging should stop as it would with long press reordering

Implementation suggestions:

  • A Modifier-extension named "dragHandle()" which attaches pointerInput() and handels everything

scrollToItem() in the ReorderableState result in no animation for first item

In the sample app, and in the Grids screen,I found that if I move the item to the first index there's no animation.
After digging into that, there's a related code snippets

internal fun onDrag(offsetX: Int, offsetY: Int) {
// ...
        if (targetItem.itemIndex == firstVisibleItemIndex || draggingItem.itemIndex == firstVisibleItemIndex) {
                scope.launch {
                    onMove.invoke(
                        ItemPosition(draggingItem.itemIndex, draggingItem.itemKey),
                        ItemPosition(targetItem.itemIndex, targetItem.itemKey)
                    )

                    scrollToItem(firstVisibleItemIndex, firstVisibleItemScrollOffset) // CHECK THIS
                }
            } else {
                onMove.invoke(
                    ItemPosition(draggingItem.itemIndex, draggingItem.itemKey),
                    ItemPosition(targetItem.itemIndex, targetItem.itemKey)
                )
            }

If I remove the scrollToItem(), the animation works.
Why is this needed here? What's the problem to resolve?

Can you provide a configuration to disable the scrollToItem?

crashed with gradlew installDebug command: java.lang.NoSuchMethodError: No static method animateItemPlacement$default

2022-11-03 08:36:22.143 13314-13314/org.burnoutcrew.android E/AndroidRuntime: FATAL EXCEPTION: main
Process: org.burnoutcrew.android, PID: 13314
java.lang.NoSuchMethodError: No static method animateItemPlacement$default(Landroidx/compose/foundation/lazy/LazyItemScope;Landroidx/compose/ui/Modifier;Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Landroidx/compose/ui/Modifier; in class Landroidx/compose/foundation/lazy/LazyItemScope; or its super classes (declaration of 'androidx.compose.foundation.lazy.LazyItemScope' appears in /data/app/~~1A9TlYf1XD0Jlu_lfHi_4w==/org.burnoutcrew.android-i4GHexoCtb4Q1vTaG5MPPA==/base.apk)
at org.burnoutcrew.reorderable.ReorderableItemKt.ReorderableItem(ReorderableItem.kt:37)
at org.burnoutcrew.android.ui.reorderlist.ReorderListKt$NewHorizontalReorderList$1$invoke$$inlined$items$default$4.invoke(LazyDsl.kt:424)
at org.burnoutcrew.android.ui.reorderlist.ReorderListKt$NewHorizontalReorderList$1$invoke$$inlined$items$default$4.invoke(LazyDsl.kt:145)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:135)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.foundation.lazy.LazyListItemsSnapshot.Item(LazyListItemProviderImpl.kt:90)
at androidx.compose.foundation.lazy.LazyListItemProviderImpl.Item(LazyListItemProviderImpl.kt:117)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1$1.invoke(LazyLayoutItemContentFactory.kt:119)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1$1.invoke(LazyLayoutItemContentFactory.kt:118)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1.invoke(LazyLayoutItemContentFactory.kt:118)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1.invoke(LazyLayoutItemContentFactory.kt:110)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$2$1$1.invoke(SubcomposeLayout.kt:770)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$2$1$1.invoke(SubcomposeLayout.kt:448)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3248)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3173)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:950)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3848)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3848)
2022-11-03 08:36:22.145 13314-13314/org.burnoutcrew.android E/AndroidRuntime: at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:468)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:441)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:432)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:421)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:732)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:118)
at androidx.compose.foundation.lazy.LazyMeasuredItemProvider.getAndMeasure-ZjPyQlc(LazyMeasuredItemProvider.kt:47)
at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-7Xnphek(LazyListMeasure.kt:151)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyList.kt:304)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyList.kt:197)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke-0kLqBqw(LazyLayout.kt:74)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke(LazyLayout.kt:70)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:590)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke-3p2s80s(AndroidOverscroll.kt:535)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke(AndroidOverscroll.kt:534)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:285)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke-3p2s80s(AndroidOverscroll.kt:519)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke(AndroidOverscroll.kt:518)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:285)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:405)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(Padding.kt:364)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.kt:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:94)
2022-11-03 08:36:22.147 13314-13314/org.burnoutcrew.android E/AndroidRuntime: at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1366)
at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImpl.kt:89)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.kt:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1366)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:115)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
at androidx.compose.ui.graphics.BlockGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:342)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.kt:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1366)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:115)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(Padding.kt:364)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1428)
2022-11-03 08:36:22.148 13314-13314/org.burnoutcrew.android E/AndroidRuntime: at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.kt:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1366)
at androidx.compose.material.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(Scaffold.kt:323)
at androidx.compose.material.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(Scaffold.kt:243)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.kt:70)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure$1.placeChildren(SubcomposeLayout.kt:602)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.kt:968)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.kt:953)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutSnapshotReads$ui_release(OwnerSnapshotObserver.kt:52)
at androidx.compose.ui.node.LayoutNode.layoutChildren$ui_release(LayoutNode.kt:953)
at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.kt:938)
at androidx.compose.ui.node.InnerPlaceable.placeAt-f8xVGno(InnerPlaceable.kt:79)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.kt:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.kt:370)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.kt:161)
at androidx.compose.ui.node.OuterMeasurablePlaceable.access$placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.kt:28)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.kt:149)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.kt:148)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
2022-11-03 08:36:22.150 13314-13314/org.burnoutcrew.android E/AndroidRuntime: at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutModifierSnapshotReads$ui_release(OwnerSnapshotObserver.kt:59)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeAt-f8xVGno(OuterMeasurablePlaceable.kt:148)
at androidx.compose.ui.node.OuterMeasurablePlaceable.replace(OuterMeasurablePlaceable.kt:173)
at androidx.compose.ui.node.LayoutNode.replace$ui_release(LayoutNode.kt:826)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:280)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:38)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:208)
at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:757)
at androidx.compose.ui.node.Owner$-CC.measureAndLayout$default(Owner.kt:196)
at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:954)
at android.view.View.draw(View.java:22707)
at android.view.View.updateDisplayListIfDirty(View.java:21579)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
at android.view.View.updateDisplayListIfDirty(View.java:21535)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
at android.view.View.updateDisplayListIfDirty(View.java:21535)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
at android.view.View.updateDisplayListIfDirty(View.java:21535)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
at android.view.View.updateDisplayListIfDirty(View.java:21535)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:616)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:4531)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4251)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3374)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2179)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8793)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:780)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

reproduce step:

  1. git clone https://github.com/aclassen/ComposeReorderable.git
  2. cd ComposeReorderable
  3. execute ./gradlew installDebug , or android studio : gradle task-> ComposeReorderList-> android-> Tasks-> install -> installDebug, (it would not crash if you click the run button )
  4. open the Lazy reorderable app, it will crash

Android os: android 12
build system: MacOS 12.6 (m1 max , macbook pro 16)
it will crash if I use the library in my project when it try compile it with .gradlew installDebug

Library Not Completely Multiplatform

I placed the library import in common source set and I can't access reorder state

but I can do that inside androidMain and desktopMain , What do you think is the issue !

Errors when opening Project in Android Studio

I was trying to open your project in Android Studio to try and understand how it works but I always get an Unresolved reference error with any Compose-related library in the reorderable package. Have you maybe encountered this problem before and have an idea on how to fix it?
image

Simple Column Support ?

I don't want to use a lazy list and need to use a simple column but I cannot do that
I have parent scrollable column and I don't want to add another lazy column in there because I don't want it to be scrollable

Could you please support Simple Column

Supporting RTL

Thanks great library.

But When I Use it in LazyRow with RTL Language/Layout.

It's not working properly.

Please add support for RTL 👍🏻.

Animation is not working. Any idea?

Animation is not working. Any idea? isDraggigng is always false

LazyColumn(
        state = state.listState,
        modifier = Modifier
            .reorderable(state)
            .detectReorderAfterLongPress(state)
    ) {
        items(data.value.size, { data.value[it] }) { item ->
            ReorderableItem(state, key = item) { isDragging ->
                val elevation = animateDpAsState(if (isDragging) 46.dp else 0.dp)
                Column(
                    modifier = Modifier
                        .shadow(elevation.value)
                        .background(MaterialTheme.colorScheme.surface)
                ) {
                    ShowCategoryDivider(data.value[item])
                }
            }
        }
    }

detectReorder by random int position

How to change .detectReorderAfterLongPress(stateX) by change 'stateX'?

val positionX = (0...items.itemcount-1).random() (all items show in screen)
Long click on phone screen,
How to change stateX to detectReorderAfterLongPress with positionX
and get dragging item at positionX?

Thank you so much!

Main branch does not build

Android Studio Version: Android Studio Chipmunk | 2021.2.1 Patch 1
Machine: MacBook Pro (13-inch, M1, 2020), with Apple M1 chip.

image

Support For LazyStaggeredGrid

LazyStaggeredGrid is out in 1.3.2-beta02 in Compose Foundation , I'm hoping it'll arrive soon in jetpack version of compose since its on 1.3.0-alpha01
I was hoping you could support LazyStaggeredGrid !

forEachGesture deprecated

Is it possible to change the deprecated call ?

And the awaitPointerEventScope cause a warning in Android Studio : "Returning from awaitPointerEventScope may cause some input events to be dropped"

Can you have a look ?

Incorrect index calculation in case item{} function is used with items{} in LazyColumn

In case
LazyColumn(state = listState, modifier = Modifier.reorderable(orderState, { a, b -> onReorder(models, a, b) })) {
item{
ListHeader()
}
items(models.size, { index -> getKey(models[index]) }) { index ->
ListItem(model = item, index)
}
}

To achieve header on top of the list, beyond which you can't drag the item. After starting the drag motion, item dragged will have position index+1 and possibly cause outOfIndexException in case you drag the item to last poistion in function .move(to, from)

NullPointerException when dragging!

So here is a stack trace.

java.lang.NullPointerException
        at androidx.compose.ui.node.DelegatableNodeKt.requireOwner(DelegatableNode.kt:308)
        at androidx.compose.ui.node.SemanticsModifierNodeKt.invalidateSemantics(SemanticsModifierNode.kt:44)
        at androidx.compose.ui.node.NodeKindKt.autoInvalidateNode(NodeKind.kt:171)
        at androidx.compose.ui.node.NodeChain.updateNodeAndReplaceIfNeeded(NodeChain.kt:513)
        at androidx.compose.ui.node.NodeChain.updateFrom$ui_release(NodeChain.kt:130)
        at androidx.compose.ui.node.LayoutNode.setModifier(LayoutNode.kt:735)
        at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42)
        at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42)
        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1668)
        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1666)
        at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:808)
        at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:587)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:505)
        at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
        at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
        at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
        at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:973)
        at android.view.Choreographer.doCallbacks(Choreographer.java:799)
        at android.view.Choreographer.doFrame(Choreographer.java:730)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:960)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:236)
        at android.app.ActivityThread.main(ActivityThread.java:8057)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
    	Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@98d8918, androidx.compose.ui.platform.MotionDurationScaleImpl@198df71, StandaloneCoroutine{Cancelling}@491c156, AndroidUiDispatcher@97bb5d7]

I don't see how it could be like that at all.

I can move locked items

I can move locked grid items.

If you move the elements that are before the blocked element behind it, then the blocked element moves to the beginning of the grid.

If you move elements after a blocked element in front of it, then the blocked element moves to the end of the grid.

Thus, I can move the blocked element to the beginning or end, and only after that I will not be able to move it anywhere.

Perhaps similar behavior will be in lists.

This problem is reproduced by the example code.

grid_reorder-2023-04-27_18 04 47_sm

In addition, there is another strange observation.

Apparently, I can literally drag a blocked element if I start to do so from the near edge of an adjacent element. This also works on regular elements, i.e. I click on one element, and the adjacent element is dragged.

grid_reorder_1-2023-04-27_18 39 20

Swipe to Dismiss

When swipe to dismiss used, and dragging item from top to bottom, draggable item is going under the lower item, dragging upwards is ok

Am I doing somethiing wrong?

        LazyColumn(
            state = state.listState,
            modifier = Modifier
                .fillMaxHeight()
                .background(Red50)
                .then(Modifier.reorderable(state)),
            contentPadding = PaddingValues(5.dp),
            verticalArrangement = Arrangement.spacedBy(5.dp),
        ) {

            items(exercisePlans, { it }) { item ->

                val dismissState = rememberDismissState()

                if (dismissState.isDismissed(DismissDirection.EndToStart)) {
                    //notesList.remove(item)
                }

                SwipeToDismiss(
                    state = dismissState,
                    modifier = Modifier
                        .padding(vertical = 1.dp),
                    directions = setOf(
                        DismissDirection.EndToStart
                    ),

                    background = {


                    },
                    dismissContent = {
                        ReorderableItem(state, item) { isDragging ->
                            val elevation = animateDpAsState(if (isDragging) 8.dp else 0.dp)
                            Row(
                                modifier = Modifier
                                    .shadow(elevation.value)
                                    .background(
                                        color = Color.White,
                                        shape = RoundedCornerShape(14.dp)
                                    )
                                    .padding(10.dp)
                                    .detectReorderAfterLongPress(state)
                                    .fillMaxWidth(),
                                verticalAlignment = Alignment.CenterVertically
                            ) {
                                Image(
                                    Icons.Default.List,
                                    "",
                                    colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onBackground),
                                    modifier = Modifier
                                        .detectReorder(state)
                                        .padding(start = 10.dp)
                                )
                                Text(
                                    text = item.exerciseUiModel.name,
                                    modifier = Modifier.padding(16.dp)
                                )
                            }

                        }
                    }
                )

            }
        }

chooseDropItem implementation for ReorderableLazyGridState

First of all, thanks for good library!
I implemented it for 3-column grid with square items and I found that it's a little bit inconvenient to force target item to move when dragging. You should drag your item far enough to do that. IMO it is better for UX if you consider distance between draggable and target items. I implemented it this way:

fun Int.dp() = (Resources.getSystem().displayMetrics.density * this)

override fun chooseDropItem(
        draggedItemInfo: LazyGridItemInfo?,
        items: List<LazyGridItemInfo>,
        curX: Int,
        curY: Int
    ): LazyGridItemInfo? {
        if (draggedItemInfo == null) {
            return if (draggingItemIndex != null) items.lastOrNull() else null
        }
        var target: LazyGridItemInfo? = null
        var lowScore = 36.dp() // todo may depends on cell size, consider as parameter
        val centerX = curX + draggedItemInfo.width / 2
        val centerY = curY + draggedItemInfo.height / 2

        items.forEach { item ->
            val dx = centerX - (item.right + item.left) / 2f
            val dy = centerY - (item.top + item.bottom) / 2f
            val score = hypot(dx, dy)
            if (score < lowScore) {
                lowScore = score
                target = item
            }
        }
        return target
    }

I'm not creating PR because may be my implementation is just secial case and base works as intended. And I don't know how it would be better on desktop.

Using LazyColumn.contentPadding shifts draggable position

If contentPadding used on a LazyColumn (to set status bar padding & navigation bar padding for example), draggable items's positions are shifted.

So if draggable items height is 32.dp, and LazyColumn(contentPadding=PaddingValues(top=32.dp)) is used, trying to drag the first item actually drags the second, or whatever item is next.

Additional Items In The Lazy List

I have a lazy column which works if I remove two items I added using item block otherwise I get an error

LazyColumn {
     item { } // first item

     item { } // second item

     // items
     itemsIndexed(state.editorState.blocks) { index, block ->
         BlockComponent(block)
     }
 }

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: index: 4, size: 4
	at androidx.compose.runtime.external.kotlinx.collections.immutable.internal.ListImplementation.checkElementIndex$runtime(ListImplementation.kt:15)
	at androidx.compose.runtime.external.kotlinx.collections.immutable.implementations.immutableList.SmallPersistentVector.get(SmallPersistentVector.kt:146)
	at androidx.compose.runtime.snapshots.SnapshotStateList.get(SnapshotStateList.kt:72)
	at org.burnoutcrew.reorderable.MoveKt.move(Move.kt:25)
	at com.wakaztahir.markdowntext.editor.components.EditorKt$ProvideLazyEditor$1$1$lazyEditor$1.invoke(Editor.kt:34)
	at com.wakaztahir.markdowntext.editor.components.EditorKt$ProvideLazyEditor$1$1$lazyEditor$1.invoke(Editor.kt:33)
	at org.burnoutcrew.reorderable.ReorderLogic.checkIfMoved(ReorderLogic.kt:89)
	at org.burnoutcrew.reorderable.ReorderLogic.dragBy(ReorderLogic.kt:43)
	at org.burnoutcrew.reorderable.ReorderableKt$reorderable$1$1$invokeSuspend$$inlined$collect$1.emit(Collect.kt:144)
	at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:62)
	at kotlinx.coroutines.flow.FlowKt__ChannelsKt.access$emitAllImpl$FlowKt__ChannelsKt(Channels.kt:1)
	at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Channels.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2$1.invoke(CoroutineDispatchers.skiko.kt:51)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2$1.invoke(CoroutineDispatchers.skiko.kt:46)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher.performRun(CoroutineDispatchers.skiko.kt:78)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher.access$performRun(CoroutineDispatchers.skiko.kt:29)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2.invokeSuspend(CoroutineDispatchers.skiko.kt:46)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Screenshot 2022-01-12 111826

Image flickers when reordering

I'm not sure if this is a problem with the library or a problem with how I've set it up, but I have a list of items that have a different logo associated with each item I get from an api. The image seems to flicker when reordering and my guess is because the reorder causes the position of the item to change in the state, which causes a recomposition and another load of the image.

(Using compose Coil to load images with rememberImagePainter(url))
This is definitely due to recomposition of a new url to Coil. Any suggestions?
I'll try and post a gif of it happening tomorrow

Troubles when using ComposeReorderable with a mutable list

Hello mate!
First of all, great job. Found this feature for compose took some time and your code really solve my problem.
However I have arrived to a death point because when using your code. My list state gets corrupted has soon has I add a new item to the list. The index info gets fucked up and everything starts to work really weird.
I tried to use some key to re-evaluate the list state, but everything gets reset has you wrote down on the Notes of the README.

My problem comes when my isDragEnabled and canDragOver could vary based on the actual order list. This is because I have 2 types of items on my list. Headers and Items. You can check the code here:


@Composable
fun WorkoutSetItemsList(
    onRoundEditClick: (RoundIndex) -> Unit,
    onAddExerciseClick: (RoundIndex) -> Unit,
    onExerciseClick: (RoundIndex, ExerciseExecution) -> Unit,
    onExerciseMoved: (ExerciseIndex, ExerciseIndex) -> Unit,
    viewItems: List<SetWorkoutExercisesRows>,
) {

    val state: ReorderableState = rememberReorderState(
        onMove = { from, to ->
            Grove.d { "Moving from $from to $to" }
            onExerciseMoved(from, to)
        },
        isDragEnabled = { index ->
            viewItems.getOrNull(index)?.let {
                it is SetWorkoutExercisesRows.ExerciseRow
            } == true
        },
        canDragOver = { index -> viewItems.get(index) is SetWorkoutExercisesRows.ExerciseRow })

    LazyColumn(
        state = state.listState,
        modifier = Modifier
            .fillMaxWidth()
            .reorderable(state)
    ) {
        itemsIndexed(items = viewItems, itemContent = { index, item ->
            when (item) {
                is SetWorkoutExercisesRows.RoundHeader -> {
                  //Header Item
                }
                is SetWorkoutExercisesRows.ExerciseRow -> {
                   //ExerciseItem
                }
            }
        })
    }

My problem comes when Im dragging and I drag an item. The index info doesnt get re-evaluated on the lambdas. So when my isDragEnabled calls, it returns false because it takes the previous index that a header had, and not the new one. So it doesnt allow me to scroll again in the items close to a header(because the index got fucked). Attaching u a video to show u!:

WhatsApp.Video.2021-07-30.at.10.54.31.AM.mp4

It also happens when I add new items to the list, somehow the indexes got a bit fucked and start to do weird effect.
Hope u can give me a hand or we can find together a solution

LazyVerticalGrid crashes when removing item

Application crashes when removing item from LazyVerticalGrid. The Exception is very sensitive to where is the last item postioned.The last item must be on scroll edge. Otherwise the bug doesn't seems to appear. The bug could be connected to item removel animation.

Possibly a bug in the Compose itself. The Item count was not properly updated require(itemIndex < totalSize) (Exception cause). When I use plain LazyVerticalGrid without Reorderable everyting seems to work.

Keep up the good work.

Reproduction repository: https://github.com/VeselyJan92/VerticalGridExceptionReproduction/tree/master
Google IssueTracker: https://issuetracker.google.com/issues/257488930

Reproduction

  • Use Pixel 5 API 33 as device in Android Studio
  • Build and install application
  • Click on green Item B: 0
  • The application crashes with fol
java.lang.IllegalArgumentException: Failed requirement.
        at androidx.compose.foundation.lazy.grid.LazyGridSpanLayoutProvider.getLineIndexOfItem--_Ze7BM(LazyGridSpanLayoutProvider.kt:174)
        at androidx.compose.foundation.lazy.grid.LazyGridItemPlacementAnimatorKt.lastIndexInPreviousLineBefore(LazyGridItemPlacementAnimator.kt:489)
        at androidx.compose.foundation.lazy.grid.LazyGridItemPlacementAnimatorKt.access$lastIndexInPreviousLineBefore(LazyGridItemPlacementAnimator.kt:1)
        at androidx.compose.foundation.lazy.grid.LazyGridItemPlacementAnimator.calculateExpectedOffset-xfIKQeg(LazyGridItemPlacementAnimator.kt:370)
        at androidx.compose.foundation.lazy.grid.LazyGridItemPlacementAnimator.onMeasured(LazyGridItemPlacementAnimator.kt:160)
        at androidx.compose.foundation.lazy.grid.LazyGridMeasureKt.measureLazyGrid-0cYbdkg(LazyGridMeasure.kt:241)
        at androidx.compose.foundation.lazy.grid.LazyGridKt$rememberLazyGridMeasurePolicy$1$1.invoke-0kLqBqw(LazyGrid.kt:334)
        at androidx.compose.foundation.lazy.grid.LazyGridKt$rememberLazyGridMeasurePolicy$1$1.invoke(LazyGrid.kt:184)
        at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$1$2$1.invoke-0kLqBqw(LazyLayout.kt:71)
        at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$1$2$1.invoke(LazyLayout.kt:69)
        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:591)
        at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:103)
        at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke-3p2s80s(AndroidOverscroll.kt:580)
        at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke(AndroidOverscroll.kt:579)
        at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:285)
        at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:343)
        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
        at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke-3p2s80s(AndroidOverscroll.kt:564)
        at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke(AndroidOverscroll.kt:563)
        at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:285)
        at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:343)
        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
        at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:405)
        at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:343)
        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1077)
        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1073)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2139)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:126)
        at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
        at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
E/AndroidRuntime:     at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
        at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1073)
        at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:341)
        at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1135)
        at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release$default(LayoutNode.kt:1126)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:309)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:434)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:39)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:330)
        at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:774)
        at androidx.compose.ui.node.Owner.measureAndLayout$default(Owner.kt:216)
        at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:999)
        at android.view.View.draw(View.java:23198)
        at android.view.View.updateDisplayListIfDirty(View.java:22062)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4513)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4486)
        at android.view.View.updateDisplayListIfDirty(View.java:22018)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4513)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4486)
        at android.view.View.updateDisplayListIfDirty(View.java:22018)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4513)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4486)
        at android.view.View.updateDisplayListIfDirty(View.java:22018)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4513)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4486)
        at android.view.View.updateDisplayListIfDirty(View.java:22018)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:682)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:688)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:786)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4579)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4290)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3517)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2286)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8948)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1231)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
        at android.view.Choreographer.doCallbacks(Choreographer.java:899)
        at android.view.Choreographer.doFrame(Choreographer.java:832)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
        at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Main Branch Missing Android Library Publishing

https://github.com/aclassen/ComposeReorderable/blob/main/reorderable/build.gradle.kts#L15

I realized it after forking and then publishing this library that build.gradle.kts is missing android configuration in kotlin block

android {
    publishLibraryVariants("release")
}

This can be added if android library plugin is present

plugins {
    id("com.android.library")
}

The library works fine but in debug but release versions of the app gives this exception upon usage

Capture

its also missing the following Android Block , it should probably have a manifest file as well

android {
    compileSdk = 33
    defaultConfig {
        minSdk = 21
    }
}

I addressed these issues in my fork in the following commit Qawaz@d4b01c3

Reordering animation

Can we add animation support for reordering items when dragging? animateItemPlacement() is not working for me.

Drag by handle

if I put detect modifier on the item that needs to be dragged the whole item becomes draggable , There's no space left for scrolling the lazy column , lazy column does not detect any scrolling because all the items are filling all the width and height and detecting scroll events to make themselves reorderable.

If I could put detect modifier on one composable inside the item , let's say an icon but the whole item should drag along with it (which is not the case at the moment)

This is a big problem !

Api feature request

Please, add graggable item position parameter to callback canDragOver in class ReorderableState.
It is necessary for flexible work.

abstract class ReorderableState<T>(
    private val scope: CoroutineScope,
    private val maxScrollPerFrame: Float,
    private val onMove: (fromIndex: ItemPosition, toIndex: ItemPosition) -> (Unit),

    // here
    private val canDragOver: ((indexFrom: ItemPosition, indexTo: ItemPosition) -> Boolean)?,

    private val onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))?,
    val dragCancelledAnimation: DragCancelledAnimation
) 

Drag Wrong while other item{} exist

Consider this code, when you drag item can't detect correct item !

   LazyColumn(
        state = state.listState,
        modifier = Modifier
            .reorderable(state)
            .detectReorderAfterLongPress(state)
    ) {

        item {
              ...
        }

        item {
              ...
        }

        items(data.value, { it.uid }) { item ->
            ReorderableItem(state, key = item.uid) { isDragging ->
                val elevation = animateDpAsState(if (isDragging) 16.dp else 0.dp)
                Column(
                    modifier = Modifier
                        .shadow(elevation.value)
                        .background(MaterialTheme.colors.surface)
                ) {
                        ...
                }
            }
        }
    }

java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/compose/foundation/lazy/grid/LazyGridItemScope$DefaultImpls;

at org.burnoutcrew.reorderable.ReorderableItemKt.ReorderableItem(ReorderableItem.kt:47)
at presentation.saleCreate.pickSalePhoto.ReorderGridKt$HorizontalGrid$1$invoke$$inlined$items$default$5.invoke(LazyGridDsl.kt:494)
at presentation.saleCreate.pickSalePhoto.ReorderGridKt$HorizontalGrid$1$invoke$$inlined$items$default$5.invoke(LazyGridDsl.kt:391)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:135)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.foundation.lazy.grid.LazyGridItemsSnapshot.Item(LazyGridItemProviderImpl.kt:97)
at androidx.compose.foundation.lazy.grid.LazyGridItemProviderImpl.Item(LazyGridItemProviderImpl.kt:119)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1$1.invoke(LazyLayoutItemContentFactory.kt:119)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1$1.invoke(LazyLayoutItemContentFactory.kt:118)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1.invoke(LazyLayoutItemContentFactory.kt:118)
at androidx.compose.foundation.lazy.layout.LazyLayoutItemContentFactory$CachedItemContent$createContentLambda$1.invoke(LazyLayoutItemContentFactory.kt:110)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$2$1$1.invoke(SubcomposeLayout.kt:770)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$2$1$1.invoke(SubcomposeLayout.kt:448)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:74)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3193)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3183)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3183)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3119)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:584)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:811)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3712)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3712)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:468)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:441)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:432)

Reoordering not working in example code for lists and grids but for fixed

Hi,
reordering does not work for 'Lists' and 'Grids' for me. Instead vertical and horizontal lists are scrolled as well as grids. For 'Fixed' it works when the handle on the left is used. Which seems intentional.
I used the unmodified example by cloning on Android Studio (Flamingo 2022.2.1 Beta 5 as of March 2, 2023) with an emulator running Android 12 ( APA 31) and on y physical device running Android 9 (API 28).
Any ideas of how to solve?
Greetings, Jens-Uwe

Version 0.9.1 causes infinite recomposition.

I have not yet debugged what causes this, but in 0.9.1 reading the rememberReorderableLazyListState from the lazycolumn will cause infinite recomposition.

    val reorderState = rememberReorderableLazyListState(
        listState = lazyListState,
        onDragEnd = { from, to ->  },
        onMove = { from, to ->  }
    )

    LazyColumn(
        state = reorderState.listState,
        contentPadding = PaddingValues(bottom = 8.dp),
        modifier = Modifier.reorderable(state = reorderState)
    ) {
        item {
            header()
        }
        itemsIndexed(items = playlistEntriesItems, key = { _, item -> item.id }) { index, item ->
            ReorderableItem(reorderState, item.id) { isDragging ->
}
}
}

No issue with 0.9.0

Using mutableStateListOf will not have a smooth animation

The examples show using a mutableStateOf<List<*>>() but it would be nice to use mutableStateListOf. When I tried to use it, the animation of reordering is not smooth anymore and the element will just jump to the next position. If I switch to using mutableStateOf it will be smooth again. For reference here is what I tried:

val exampleList = remember {
   list.toMutableStateList()
}
val onMove: (ItemPosition, ItemPosition) -> Unit = { from, to ->
    exampleList.add(to.index, exampleList.removeAt(from.index))
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.