Remove old drag&drop code.
This commit is contained in:
parent
ac71765c44
commit
b27149edaf
12 changed files with 18 additions and 1324 deletions
|
|
@ -59,7 +59,6 @@ import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
|||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.KeepOnScreen
|
||||
import com.pixelized.rplexicon.ui.composable.Loader
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.RollResult
|
||||
import com.pixelized.rplexicon.ui.composable.Toolbar
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleSkillEditDialog
|
||||
|
|
@ -84,12 +83,15 @@ import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewMode
|
|||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryPage2
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryPage
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryPreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD.InventoryViewModelOLD
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_detail.ItemDetailViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemListViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPage
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.RollResult
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.lexiconShadow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -109,7 +111,9 @@ enum class CharacterTabUio(@StringRes val label: Int) {
|
|||
@Composable
|
||||
fun CharacterSheetScreen(
|
||||
viewModel: CharacterSheetViewModel = hiltViewModel(),
|
||||
inventoryViewModel: InventoryViewModelOLD = hiltViewModel(),
|
||||
inventoryViewModel: InventoryViewModel = hiltViewModel(),
|
||||
itemListViewModel: ItemListViewModel = hiltViewModel(),
|
||||
itemDetailViewModel: ItemDetailViewModel = hiltViewModel(),
|
||||
proficiencyViewModel: ProficiencyViewModel = hiltViewModel(),
|
||||
headerViewModel: HeaderViewModel = hiltViewModel(),
|
||||
attacksViewModel: AttacksViewModel = hiltViewModel(),
|
||||
|
|
@ -202,7 +206,11 @@ fun CharacterSheetScreen(
|
|||
)
|
||||
},
|
||||
inventory = {
|
||||
InventoryPage2()
|
||||
InventoryPage(
|
||||
inventoryViewModel = inventoryViewModel,
|
||||
itemListViewModel = itemListViewModel,
|
||||
itemDetailViewModel = itemDetailViewModel,
|
||||
)
|
||||
},
|
||||
sheet = {
|
||||
SpellLevelChooser(
|
||||
|
|
|
|||
|
|
@ -1,176 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD.InventoryItemUioOLD
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberInventoryListState(): State<List<InventoryItemUioOLD>> {
|
||||
return remember {
|
||||
var id = 0
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Pouch",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 3,
|
||||
container = true,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_pouch_a_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Backpack",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 6,
|
||||
container = true,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_backpack_a_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Scroll of blessing",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = true,
|
||||
icon = R.drawable.icbg_scroll_of_bless_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Scroll of spirit weapon",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = true,
|
||||
icon = R.drawable.icbg_book_signedtradebisa_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Potion of healing",
|
||||
context = null,
|
||||
amount = 2,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = true,
|
||||
icon = R.drawable.icbg_potion_of_superior_healing_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Potion of supérior healing",
|
||||
context = null,
|
||||
amount = 2,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = true,
|
||||
icon = R.drawable.icbg_pot_potion_of_healing_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Potion of holy water",
|
||||
context = null,
|
||||
amount = 2,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = true,
|
||||
icon = R.drawable.icbg_grn_holy_water_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Leather armor",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_leather_armour_rogue_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Silver battleaxe",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_battleaxe_plus_one_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Hand crossbow",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_hand_crossbow_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Goodberry",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_food_goodberry_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Wolf fang",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_worg_fang_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Lantern of revealing",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.ic_drive_lantern_of_revealing,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "${id++}",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Dust of disappearance",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_haste_spore_grenade_unfaded,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.factory
|
||||
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.data.model.item.Item
|
||||
import com.pixelized.rplexicon.data.network.FireItemDto
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD.InventoryItemUioOLD
|
||||
import javax.inject.Inject
|
||||
|
||||
class InventoryItemUioFactory @Inject constructor() {
|
||||
|
||||
@Deprecated(message = "")
|
||||
fun toUio(
|
||||
items: Map<String, Item>,
|
||||
fires: List<FireItemDto>,
|
||||
path: InventoryPath,
|
||||
): List<InventoryItemUioOLD> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
|
||||
@Composable
|
||||
fun InventoryPage2(
|
||||
fun InventoryPage(
|
||||
inventoryViewModel: InventoryViewModel = hiltViewModel(),
|
||||
itemListViewModel: ItemListViewModel = hiltViewModel(),
|
||||
itemDetailViewModel: ItemDetailViewModel = hiltViewModel(),
|
||||
|
|
@ -92,6 +92,7 @@ fun InventoryPage2(
|
|||
}
|
||||
},
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.character.pages.inventory.composable.
|
|||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.VectorConverter
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||
|
|
@ -22,7 +23,6 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.toOffset
|
||||
import com.pixelized.rplexicon.utilitary.lazyGridDragAndDrop.detectDragGesturesAfterLongPress
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Composable
|
||||
|
|
@ -193,7 +193,7 @@ class InventoryDragDropState internal constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun onDragEnd(offset: Offset) {
|
||||
fun onDragEnd() {
|
||||
val draggedItemTargetOffset = draggedItemOffset()
|
||||
val draggedItemTargetType = type(offset = draggedItemTargetOffset)
|
||||
val layout = layoutInfo(type = draggedItemTargetType)
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ class ItemListViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun show() {
|
||||
filter.value = ""
|
||||
_display.value = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD
|
||||
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.data.model.item.Item
|
||||
import com.pixelized.rplexicon.data.network.FireItemDto
|
||||
import com.pixelized.rplexicon.data.repository.character.ItemsRepository
|
||||
import com.pixelized.rplexicon.data.repository.firebase.inventory.InventoryFireRepository
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.InventoryItemUioFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Deprecated(message = "to be removed")
|
||||
class InventoryControllerOLD(
|
||||
private val fireRepository: InventoryFireRepository,
|
||||
private val itemFactory: InventoryItemUioFactory,
|
||||
val scope: CoroutineScope,
|
||||
val character: String,
|
||||
val path: InventoryPath,
|
||||
itemsRepository: ItemsRepository,
|
||||
) {
|
||||
private val fireCollectorJob: Job
|
||||
|
||||
// Target the local indexes form specific ids (change locally to avoid flooding of firebase)
|
||||
private val localIndex = MutableStateFlow<Map<String, Int>>(hashMapOf())
|
||||
|
||||
// Item list flow. this is a the global list of all items.
|
||||
private val itemsList: StateFlow<Map<String, Item>> = itemsRepository.data
|
||||
|
||||
// Inventory for a specific character in firebase for a specific inventory path.
|
||||
// This is a flow of ItemDto from firebase and is the source of truth for the data.
|
||||
private val fireInventory: StateFlow<List<FireItemDto>> = emptyFlow<List<FireItemDto>>()
|
||||
// fireRepository.getInventory(character = character, path = path)
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
// Inventory for a specific character in firebase for a specific inventory path.
|
||||
// This consume other flows to create on that is displayable.
|
||||
// The goal of it is to avoid updating eagerly the firebase data.
|
||||
val inventory = combine(itemsList, fireInventory, localIndex) { items, fire, indexes ->
|
||||
itemFactory.toUio(items = items, fires = fire, path = path).sortedBy { indexes[it.id] }
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
init {
|
||||
fireCollectorJob = scope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
// fireRepository.getInventory(character = character, path = path).collect { items ->
|
||||
// localIndex.value = items.mapNotNull {
|
||||
// if (it.id != null) {
|
||||
// it.id as String to items.indexOf(it)
|
||||
// } else {
|
||||
// null
|
||||
// }
|
||||
// }.toMap()
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun finalize() {
|
||||
fireCollectorJob.cancel()
|
||||
}
|
||||
|
||||
fun onMove(
|
||||
fromIndex: Int,
|
||||
toIndex: Int,
|
||||
) {
|
||||
// Item have been moved, need to update the local item index.
|
||||
// This is call during a drag event to move item in the collection.
|
||||
// We avoid updating firebase during the drag, we do so on the release (see onDrop(Int))
|
||||
localIndex.value = localIndex.value.toMutableMap().also { map ->
|
||||
val fromId = map.firstNotNullOfOrNull { if (it.value == fromIndex) it.key else null }
|
||||
val toId = map.firstNotNullOfOrNull { if (it.value == toIndex) it.key else null }
|
||||
if (fromId != null && toId != null) {
|
||||
map[fromId] = toIndex
|
||||
map[toId] = fromIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isMoveEnable(fromIndex: Int, toIndex: Int): Boolean {
|
||||
val isSourceAContainer = inventory.value.getOrNull(fromIndex)?.container ?: false
|
||||
val isReceiverAContainer = inventory.value.getOrNull(toIndex)?.container ?: false
|
||||
return !(isSourceAContainer.not() && isReceiverAContainer)
|
||||
}
|
||||
|
||||
fun onDrop(index: Int) {
|
||||
// val source = inventory.value.getOrNull(index)
|
||||
// val fireIndex = fireInventory.value.indexOfFirst { it.id == source?.id }
|
||||
//
|
||||
// val inventory = fireInventory.value.toMutableList().also { fireInventory ->
|
||||
// fireInventory.add(index, fireInventory.removeAt(fireIndex))
|
||||
// }
|
||||
|
||||
// fireRepository.setInventory(
|
||||
// character = character,
|
||||
// path = path,
|
||||
// inventory = inventory,
|
||||
// )
|
||||
}
|
||||
|
||||
fun onDropOver(fromIndex: Int, toIndex: Int) {
|
||||
// val source = inventory.value.getOrNull(fromIndex)
|
||||
// val receiver = inventory.value.getOrNull(toIndex)
|
||||
//
|
||||
// if (fromIndex != toIndex && receiver?.container == true) {
|
||||
// val inventory = fireInventory.value.toMutableList().also { fireInventory ->
|
||||
// val sourceIndex = fireInventory.indexOfFirst { it.id == source?.id }
|
||||
// val receiverIndex = fireInventory.indexOfFirst { it.id == receiver.id }
|
||||
// val sourceFireItem = fireInventory.getOrNull(sourceIndex)
|
||||
// val receiverFireItem = fireInventory.getOrNull(receiverIndex)
|
||||
// if (sourceFireItem != null && receiverFireItem != null) {
|
||||
// fireInventory.set(
|
||||
// index = receiverIndex,
|
||||
// element = receiverFireItem.copy(
|
||||
// children = receiverFireItem.children.toMutableList().also {
|
||||
// it.add(sourceFireItem)
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
// fireInventory.remove(element = sourceFireItem)
|
||||
// }
|
||||
// }
|
||||
// fireRepository.setInventory(
|
||||
// character = character,
|
||||
// path = path,
|
||||
// inventory = inventory,
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.ui.composable.images.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
@Deprecated(message = "to be removed")
|
||||
data class InventoryItemUioOLD(
|
||||
val id: String,
|
||||
val path: InventoryPath,
|
||||
val name: String,
|
||||
val context: String?,
|
||||
val amount: Int,
|
||||
val items: Int,
|
||||
val container: Boolean,
|
||||
val usable: Boolean,
|
||||
val icon: Any? = R.drawable.icbg_generic_darkness_icon,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Deprecated(message = "to be removed")
|
||||
fun InventoryItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: InventoryItemUioOLD,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.then(other = modifier)
|
||||
.aspectRatio(ratio = 1f),
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
model = item.icon,
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
AnimatedContent(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.TopEnd)
|
||||
.offset(x = 0.dp, y = (-4).dp)
|
||||
.padding(horizontal = 2.dp),
|
||||
targetState = item.items,
|
||||
transitionSpec = {
|
||||
// Compare the incoming number with the previous number.
|
||||
if (targetState > initialState) {
|
||||
// If the target number is larger, it slides up and fades in
|
||||
// while the initial (smaller) number slides up and fades out.
|
||||
slideInVertically { height -> height / 2 } + fadeIn() togetherWith slideOutVertically { height -> -height / 2 } + fadeOut()
|
||||
} else {
|
||||
// If the target number is smaller, it slides down and fades in
|
||||
// while the initial number slides down and fades out.
|
||||
slideInVertically { height -> -height / 2 } + fadeIn() togetherWith slideOutVertically { height -> height / 2 } + fadeOut()
|
||||
}.using(
|
||||
// Disable clipping since the faded slide-in/out should
|
||||
// be displayed out of bounds.
|
||||
SizeTransform(clip = false)
|
||||
)
|
||||
},
|
||||
label = "Container count size",
|
||||
) { items ->
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.End,
|
||||
text = items.takeIf { it > 0 }?.let { "$it" } ?: " ", //
|
||||
)
|
||||
}
|
||||
AnimatedContent(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.BottomEnd)
|
||||
.offset(x = 0.dp, y = 4.dp)
|
||||
.padding(horizontal = 2.dp),
|
||||
targetState = item.amount,
|
||||
transitionSpec = {
|
||||
// Compare the incoming number with the previous number.
|
||||
if (targetState > initialState) {
|
||||
// If the target number is larger, it slides up and fades in
|
||||
// while the initial (smaller) number slides up and fades out.
|
||||
slideInVertically { height -> height / 2 } + fadeIn() togetherWith slideOutVertically { height -> -height / 2 } + fadeOut()
|
||||
} else {
|
||||
// If the target number is smaller, it slides down and fades in
|
||||
// while the initial number slides down and fades out.
|
||||
slideInVertically { height -> -height / 2 } + fadeIn() togetherWith slideOutVertically { height -> height / 2 } + fadeOut()
|
||||
}.using(
|
||||
// Disable clipping since the faded slide-in/out should
|
||||
// be displayed out of bounds.
|
||||
SizeTransform(clip = false)
|
||||
)
|
||||
},
|
||||
label = "Container count size",
|
||||
) { amount ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.graphicsLayer { this.alpha = if (amount == 1) 0f else 1f },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.End,
|
||||
text = amount.takeIf { it > 1 }?.let { "$it" } ?: " ", //
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, widthDp = 64)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 64)
|
||||
private fun InventoryItemPreview(
|
||||
@PreviewParameter(ClassInventoryItemProvider::class) preview: InventoryItemUioOLD,
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
InventoryItem(
|
||||
item = preview,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassInventoryItemProvider : PreviewParameterProvider<InventoryItemUioOLD> {
|
||||
override val values: Sequence<InventoryItemUioOLD> = sequenceOf(
|
||||
InventoryItemUioOLD(
|
||||
id = "0",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Pouch",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 1,
|
||||
usable = false,
|
||||
container = true,
|
||||
icon = R.drawable.icbg_pouch_a_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "1",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Scroll of blessing",
|
||||
context = null,
|
||||
amount = 1,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_scroll_of_bless_unfaded,
|
||||
),
|
||||
InventoryItemUioOLD(
|
||||
id = "2",
|
||||
path = InventoryPath.ROOT,
|
||||
name = "Potion of blessing",
|
||||
context = null,
|
||||
amount = 2,
|
||||
items = 0,
|
||||
container = false,
|
||||
usable = false,
|
||||
icon = R.drawable.icbg_pot_potion_of_healing_unfaded,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalRollOverlay
|
||||
import com.pixelized.rplexicon.LocalSnack
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberInventoryListState
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_detail.ItemDetailDialog
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_detail.ItemDetailViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemListDialog
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemListViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemUio
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.DraggableItem
|
||||
import com.pixelized.rplexicon.utilitary.dragContainer
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import com.pixelized.rplexicon.utilitary.rememberGridDragDropState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@Deprecated(message = "to be removed")
|
||||
fun InventoryPageOLD(
|
||||
viewModel: InventoryViewModelOLD = hiltViewModel(),
|
||||
itemViewModel: ItemListViewModel = hiltViewModel(),
|
||||
detailViewModel: ItemDetailViewModel = hiltViewModel(),
|
||||
) {
|
||||
val overlay = LocalRollOverlay.current
|
||||
val snack = LocalSnack.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val path = remember { InventoryPath.ROOT }
|
||||
val controller = viewModel.controller(path = path)
|
||||
val rootInventory = viewModel.inventory(controller = controller)
|
||||
|
||||
InventoryPageContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
inventory = rootInventory,
|
||||
onAddItem = itemViewModel::show,
|
||||
onInventoryItemClick = { item: InventoryItemUioOLD ->
|
||||
// detailViewModel.show( //TODO
|
||||
// detail = viewModel.getItemDetail(item = item)
|
||||
// )
|
||||
},
|
||||
onInventoryItemMove = controller::onMove,
|
||||
onInventoryIsMoveEnable = controller::isMoveEnable,
|
||||
onInventoryItemDrop = controller::onDrop,
|
||||
onInventoryItemDropOver = controller::onDropOver,
|
||||
)
|
||||
|
||||
ItemListDialog(
|
||||
viewModel = itemViewModel,
|
||||
onItem = { item: ItemUio ->
|
||||
// detailViewModel.show(
|
||||
// detail = viewModel.getItemDetail(item = item)
|
||||
// )
|
||||
},
|
||||
onDismissRequest = itemViewModel::hide,
|
||||
)
|
||||
|
||||
ItemDetailDialog(
|
||||
viewModel = detailViewModel,
|
||||
// onItem = { item ->
|
||||
// detailViewModel.show(
|
||||
// detail = viewModel.getItemDetail(item = item)
|
||||
// )
|
||||
// },
|
||||
onDismissRequest = {
|
||||
// viewModel.changeItemQuantity(
|
||||
// id = detail.id,
|
||||
// path = detail.path,
|
||||
// quantity = detail.quantity ?: 0
|
||||
// )
|
||||
// detailViewModel.hide(detail)
|
||||
},
|
||||
onUse = { detail ->
|
||||
// detailViewModel.hide(detail)
|
||||
viewModel.onUse(itemId = detail.id)?.let { diceThrow ->
|
||||
scope.launch {
|
||||
overlay.showOverlay(diceThrow = diceThrow)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = "InventorySnack") {
|
||||
viewModel.snack.collect { message ->
|
||||
message?.let {
|
||||
scope.launch { snack.showSnackbar(message = it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InventoryPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
inventory: State<List<InventoryItemUioOLD>>,
|
||||
onAddItem: () -> Unit,
|
||||
onInventoryItemClick: (InventoryItemUioOLD) -> Unit,
|
||||
onInventoryItemMove: (Int, Int) -> Unit,
|
||||
onInventoryIsMoveEnable: (Int, Int) -> Boolean,
|
||||
onInventoryItemDrop: (Int) -> Unit,
|
||||
onInventoryItemDropOver: (Int, Int) -> Unit,
|
||||
) {
|
||||
val contentPadding = remember { PaddingValues(16.dp) }
|
||||
val gridState = rememberLazyGridState()
|
||||
val dragDropState = rememberGridDragDropState(
|
||||
contentPadding = contentPadding,
|
||||
gridState = gridState,
|
||||
onMove = onInventoryItemMove,
|
||||
isMoveEnable = onInventoryIsMoveEnable,
|
||||
onDragRelease = onInventoryItemDrop,
|
||||
onDropReleaseOver = onInventoryItemDropOver,
|
||||
)
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(5),
|
||||
modifier = Modifier
|
||||
.graphicsLayer { this.clip = false }
|
||||
.dragContainer(dragDropState)
|
||||
.fillMaxSize(),
|
||||
state = gridState,
|
||||
contentPadding = contentPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = inventory.value,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
DraggableItem(
|
||||
dragDropState = dragDropState,
|
||||
index = index
|
||||
) { isDragging, isOvering ->
|
||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||
val transition = updateTransition(
|
||||
targetState = isDragging || isOvering,
|
||||
label = "Dragging transition",
|
||||
)
|
||||
val backgroundColor = transition.animateColor(
|
||||
label = "Draggable item background color",
|
||||
) {
|
||||
val surface = colorScheme.base.surfaceColorAtElevation(2.dp)
|
||||
when (it) {
|
||||
true -> colorScheme.base.primary.copy(alpha = 0.15f)
|
||||
.compositeOver(surface)
|
||||
|
||||
else -> surface
|
||||
}
|
||||
}
|
||||
val outlineColor = transition.animateColor(
|
||||
label = "Draggable item outline color",
|
||||
) {
|
||||
when (it) {
|
||||
true -> colorScheme.base.primary
|
||||
else -> colorScheme.characterSheet.outlineBorder
|
||||
}
|
||||
}
|
||||
val innerColor = transition.animateColor(
|
||||
label = "Draggable item inline color",
|
||||
) {
|
||||
when (it) {
|
||||
true -> colorScheme.base.primary
|
||||
else -> colorScheme.characterSheet.innerBorder
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.doubleBorder(
|
||||
backgroundColor = backgroundColor.value,
|
||||
outline = remember { RoundedCornerShape(8.dp) },
|
||||
outlineColor = outlineColor.value,
|
||||
inner = remember { RoundedCornerShape(6.dp) },
|
||||
innerColor = innerColor.value,
|
||||
)
|
||||
) {
|
||||
InventoryItem(
|
||||
modifier = Modifier.clickable { onInventoryItemClick(item) },
|
||||
item = item,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.BottomEnd)
|
||||
.padding(all = 16.dp),
|
||||
onClick = onAddItem,
|
||||
expended = true,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.character_sheet__inventory__edit_items_action))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
fun InventoryPagePreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
InventoryPageContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
inventory = rememberInventoryListState(),
|
||||
onAddItem = { },
|
||||
onInventoryItemClick = { },
|
||||
onInventoryItemMove = { _, _ -> },
|
||||
onInventoryIsMoveEnable = { _, _ -> true },
|
||||
onInventoryItemDrop = { _ -> },
|
||||
onInventoryItemDropOver = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.data.model.DiceThrow
|
||||
import com.pixelized.rplexicon.data.model.item.InventoryPath
|
||||
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
|
||||
import com.pixelized.rplexicon.data.repository.character.ItemsRepository
|
||||
import com.pixelized.rplexicon.data.repository.firebase.inventory.InventoryFireRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.InventoryItemUioFactory
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@Deprecated("to be removed")
|
||||
class InventoryViewModelOLD @Inject constructor(
|
||||
private val descriptionRepository: DescriptionRepository,
|
||||
private val fireRepository: InventoryFireRepository,
|
||||
private val itemFactory: InventoryItemUioFactory,
|
||||
private val itemsRepository: ItemsRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
private val character = savedStateHandle.characterSheetArgument.name
|
||||
private val controllers = hashMapOf<InventoryPath, InventoryControllerOLD>()
|
||||
|
||||
private val _snack = MutableSharedFlow<String?>()
|
||||
val snack: SharedFlow<String?> get() = _snack
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun controller(
|
||||
path: InventoryPath,
|
||||
): InventoryControllerOLD {
|
||||
val controller = remember(path) {
|
||||
InventoryControllerOLD(
|
||||
fireRepository = fireRepository,
|
||||
itemsRepository = itemsRepository,
|
||||
itemFactory = itemFactory,
|
||||
scope = viewModelScope,
|
||||
character = character,
|
||||
path = path,
|
||||
)
|
||||
}
|
||||
DisposableEffect(key1 = path) {
|
||||
controllers[path] = controller
|
||||
onDispose {
|
||||
controllers.remove(path)
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
// UI data, merge the firebase inventory full data and with the local index and transform it into UIO.
|
||||
@Composable
|
||||
@Stable
|
||||
fun inventory(
|
||||
controller: InventoryControllerOLD,
|
||||
): State<List<InventoryItemUioOLD>> {
|
||||
return controller.inventory.collectAsState(initial = emptyList())
|
||||
}
|
||||
|
||||
fun onUse(itemId: String): DiceThrow? {
|
||||
val item = itemsRepository.find(id = itemId)
|
||||
return item?.let {
|
||||
DiceThrow.Object(
|
||||
character = character,
|
||||
itemId = item.id,
|
||||
itemName = item.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeItemQuantity(
|
||||
id: String,
|
||||
path: InventoryPath?,
|
||||
quantity: Int,
|
||||
) {
|
||||
// if (path != null) {
|
||||
// // if path != null then we target an item that is already in the inventory.
|
||||
// if (quantity > 0) {
|
||||
// // if the quantity is > 0 then only adjust it.
|
||||
// fireRepository.setItemQuantity(
|
||||
// character = character,
|
||||
// path = path,
|
||||
// id = id,
|
||||
// quantity = quantity,
|
||||
// )
|
||||
// } else {
|
||||
// // need to remove the item from the list & update the position of every elements.
|
||||
// val root = fireRepository.getInventorySnapshot(character = character)
|
||||
// val item = root.getItem(path = path)
|
||||
// val parent = root.getItem(path = path.parent())
|
||||
//
|
||||
// val updatedInventory = (parent?.children ?: root).toMutableList().also {
|
||||
// it.removeAt(it.indexOf(item))
|
||||
// }
|
||||
// fireRepository.setInventory(
|
||||
// character = character,
|
||||
// path = path.parent(), // indexPath.joinToString(separator = "") { "$it/${ItemDto.CHILDREN}/" },
|
||||
// inventory = updatedInventory,
|
||||
// )
|
||||
// }
|
||||
// } else if (quantity > 0) {
|
||||
// val root = fireRepository.getInventorySnapshot(character = character)
|
||||
// // path is null, we target an item that is NOT in the list.
|
||||
// // retrieve the last element of the list and add one after.
|
||||
// fireRepository.setItemQuantity(
|
||||
// character = character,
|
||||
// path = InventoryPath.ROOT.add(root.lastIndex + 1),
|
||||
// id = id,
|
||||
// quantity = quantity,
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
package com.pixelized.rplexicon.utilitary
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.VectorConverter
|
||||
import androidx.compose.animation.core.VisibilityThreshold
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.toOffset
|
||||
import androidx.compose.ui.unit.toSize
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.pixelized.rplexicon.utilitary.lazyGridDragAndDrop.detectDragGesturesAfterLongPress
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/***
|
||||
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyGridDragAndDropDemo.kt
|
||||
*/
|
||||
@Composable
|
||||
fun rememberGridDragDropState(
|
||||
contentPadding: PaddingValues,
|
||||
gridState: LazyGridState,
|
||||
onMove: (Int, Int) -> Unit,
|
||||
isMoveEnable: (Int, Int) -> Boolean,
|
||||
onDragRelease: (Int) -> Unit,
|
||||
onDropReleaseOver: (Int, Int) -> Unit,
|
||||
): GridDragDropState {
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val currentOnMove = rememberUpdatedState(newValue = onMove)
|
||||
val currentIsMoveEnable = rememberUpdatedState(newValue = isMoveEnable)
|
||||
val currentOnDragRelease = rememberUpdatedState(newValue = onDragRelease)
|
||||
val currentOnDropReleaseOver = rememberUpdatedState(newValue = onDropReleaseOver)
|
||||
|
||||
val state = remember(gridState, scope, currentOnMove) {
|
||||
GridDragDropState(
|
||||
contentOffset = with(density) {
|
||||
Offset(
|
||||
contentPadding.calculateTopPadding().toPx(),
|
||||
contentPadding.calculateLeftPadding(layoutDirection).toPx()
|
||||
)
|
||||
},
|
||||
scope = scope,
|
||||
state = gridState,
|
||||
onMove = currentOnMove.value,
|
||||
isMoveEnable = currentIsMoveEnable.value,
|
||||
onDragRelease = currentOnDragRelease.value,
|
||||
onDropReleaseOver = currentOnDropReleaseOver.value,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(state) {
|
||||
while (isActive) {
|
||||
val diff = state.scrollChannel.receive()
|
||||
gridState.scrollBy(diff)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
class GridDragDropState internal constructor(
|
||||
private val contentOffset: Offset,
|
||||
private val scope: CoroutineScope,
|
||||
private val state: LazyGridState,
|
||||
private val onMove: (Int, Int) -> Unit,
|
||||
private val isMoveEnable: (Int, Int) -> Boolean,
|
||||
private val onDragRelease: (Int) -> Unit,
|
||||
private val onDropReleaseOver: (Int, Int) -> Unit,
|
||||
) {
|
||||
internal val scrollChannel = Channel<Float>()
|
||||
|
||||
var overItemIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
private var previousItemIndex by mutableStateOf<Int?>(null)
|
||||
|
||||
var draggingItemIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
internal var draggingItemLingeringIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
private var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
|
||||
|
||||
private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
|
||||
|
||||
private val draggingItemLayoutInfo: LazyGridItemInfo?
|
||||
get() = state.layoutInfo.visibleItemsInfo
|
||||
.firstOrNull { it.index == draggingItemIndex }
|
||||
|
||||
internal val draggingItemOffset: Offset
|
||||
get() = draggingItemLayoutInfo
|
||||
?.let { item -> draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset() }
|
||||
?: Offset.Zero
|
||||
|
||||
internal var draggingItemSetIntoPositionAnimation =
|
||||
Animatable(initialValue = Offset.Zero, typeConverter = Offset.VectorConverter)
|
||||
private set
|
||||
|
||||
internal fun onDragStart(offset: Offset) {
|
||||
state.layoutInfo.visibleItemsInfo
|
||||
.firstItemWithOffsetOrNull(offset = offset)
|
||||
?.let {
|
||||
draggingItemIndex = it.index
|
||||
draggingItemInitialOffset = it.offset.toOffset()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onDrag(offset: Offset) {
|
||||
draggingItemDraggedDelta += offset
|
||||
|
||||
val draggingItem = draggingItemLayoutInfo ?: return
|
||||
val startOffset = draggingItem.offset.toOffset() + draggingItemOffset
|
||||
val endOffset = startOffset + draggingItem.size.toSize()
|
||||
|
||||
val middleOffset = startOffset + (endOffset - startOffset) / 2f
|
||||
val middleXOffset = middleOffset.x.toInt()
|
||||
val middleYOffset = middleOffset.y.toInt()
|
||||
|
||||
val targetItem = state.layoutInfo.visibleItemsInfo.find { item ->
|
||||
draggingItem.index != item.index &&
|
||||
item.offset.x <= middleXOffset && middleXOffset <= item.offsetEnd.x &&
|
||||
item.offset.y <= middleYOffset && middleYOffset <= item.offsetEnd.y
|
||||
}
|
||||
if (targetItem != null) {
|
||||
if (isMoveEnable.invoke(draggingItem.index, targetItem.index)) {
|
||||
onMove.invoke(draggingItem.index, targetItem.index)
|
||||
previousItemIndex = draggingItemIndex
|
||||
draggingItemIndex = targetItem.index
|
||||
} else {
|
||||
previousItemIndex = null
|
||||
overItemIndex = targetItem.index
|
||||
}
|
||||
} else {
|
||||
overItemIndex = null
|
||||
val overscroll = when {
|
||||
draggingItemDraggedDelta.y > 0 ->
|
||||
(endOffset.y - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
|
||||
|
||||
draggingItemDraggedDelta.y < 0 ->
|
||||
(startOffset.y - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
|
||||
|
||||
else -> 0f
|
||||
}
|
||||
if (overscroll != 0f) {
|
||||
scrollChannel.trySend(overscroll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onDragInterrupted(offset: Offset) {
|
||||
val localDraggingItemIndex = draggingItemIndex
|
||||
|
||||
if (localDraggingItemIndex != null) {
|
||||
val target = state.layoutInfo.visibleItemsInfo.firstItemWithOffsetOrNull(offset)
|
||||
when {
|
||||
target == null && previousItemIndex != draggingItemIndex -> {
|
||||
// release have occur on the current place of the item but outside of range
|
||||
onDragRelease(localDraggingItemIndex)
|
||||
// play a animation so the item fit in place.
|
||||
draggingItemLingeringIndex = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
draggingItemSetIntoPositionAnimation.snapTo(targetValue = startOffset)
|
||||
draggingItemSetIntoPositionAnimation.animateTo(
|
||||
targetValue = Offset.Zero,
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = Offset.VisibilityThreshold
|
||||
)
|
||||
)
|
||||
draggingItemLingeringIndex = null
|
||||
}
|
||||
}
|
||||
|
||||
target == null -> {
|
||||
// release have occur outside valid range. play and animation the make the item go back in place.
|
||||
draggingItemLingeringIndex = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
draggingItemSetIntoPositionAnimation.snapTo(targetValue = startOffset)
|
||||
draggingItemSetIntoPositionAnimation.animateTo(
|
||||
targetValue = Offset.Zero,
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = Offset.VisibilityThreshold
|
||||
)
|
||||
)
|
||||
draggingItemLingeringIndex = null
|
||||
}
|
||||
}
|
||||
|
||||
target.index == localDraggingItemIndex -> {
|
||||
// release have occur on the current place of the item
|
||||
onDragRelease(localDraggingItemIndex)
|
||||
// play a animation so the item fit in place.
|
||||
draggingItemLingeringIndex = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
draggingItemSetIntoPositionAnimation.snapTo(targetValue = startOffset)
|
||||
draggingItemSetIntoPositionAnimation.animateTo(
|
||||
targetValue = Offset.Zero,
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = Offset.VisibilityThreshold
|
||||
)
|
||||
)
|
||||
draggingItemLingeringIndex = null
|
||||
}
|
||||
}
|
||||
|
||||
target.index != localDraggingItemIndex -> {
|
||||
onDropReleaseOver(localDraggingItemIndex, target.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
previousItemIndex = null
|
||||
overItemIndex = null
|
||||
draggingItemDraggedDelta = Offset.Zero
|
||||
draggingItemIndex = null
|
||||
draggingItemInitialOffset = Offset.Zero
|
||||
}
|
||||
|
||||
private fun List<LazyGridItemInfo>.firstItemWithOffsetOrNull(
|
||||
offset: Offset,
|
||||
): LazyGridItemInfo? {
|
||||
val offsetX = offset.x.toInt() - contentOffset.x.toInt()
|
||||
val offsetY = offset.y.toInt() - contentOffset.y.toInt()
|
||||
return this.firstOrNull { item ->
|
||||
item.offset.x <= offsetX && offsetX <= item.offsetEnd.x &&
|
||||
item.offset.y <= offsetY && offsetY <= item.offsetEnd.y
|
||||
}
|
||||
}
|
||||
|
||||
private val LazyGridItemInfo.offsetEnd: IntOffset
|
||||
get() = this.offset + this.size
|
||||
}
|
||||
|
||||
private operator fun IntOffset.plus(size: IntSize): IntOffset {
|
||||
return IntOffset(x + size.width, y + size.height)
|
||||
}
|
||||
|
||||
private operator fun Offset.plus(size: Size): Offset {
|
||||
return Offset(x + size.width, y + size.height)
|
||||
}
|
||||
|
||||
fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier {
|
||||
return this then Modifier.pointerInput(dragDropState) {
|
||||
detectDragGesturesAfterLongPress(
|
||||
onDrag = { change, offset ->
|
||||
change.consume()
|
||||
dragDropState.onDrag(offset = offset)
|
||||
},
|
||||
onDragStart = { offset ->
|
||||
dragDropState.onDragStart(offset)
|
||||
},
|
||||
onDragEnd = { offset ->
|
||||
dragDropState.onDragInterrupted(offset)
|
||||
},
|
||||
onDragCancel = {
|
||||
dragDropState.onDragInterrupted(Offset.Zero)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LazyGridItemScope.DraggableItem(
|
||||
dragDropState: GridDragDropState,
|
||||
index: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (isDragging: Boolean, isOver: Boolean) -> Unit
|
||||
) {
|
||||
val dragging by remember(index, dragDropState) {
|
||||
derivedStateOf {
|
||||
index == dragDropState.draggingItemIndex
|
||||
}
|
||||
}
|
||||
val overing by remember(index, dragDropState) {
|
||||
derivedStateOf {
|
||||
index == dragDropState.overItemIndex
|
||||
}
|
||||
}
|
||||
val draggingModifier = when {
|
||||
dragging -> {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer {
|
||||
translationX = dragDropState.draggingItemOffset.x
|
||||
translationY = dragDropState.draggingItemOffset.y
|
||||
}
|
||||
}
|
||||
|
||||
index == dragDropState.draggingItemLingeringIndex -> {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer {
|
||||
translationX = dragDropState.draggingItemSetIntoPositionAnimation.value.x
|
||||
translationY = dragDropState.draggingItemSetIntoPositionAnimation.value.y
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Modifier.animateItemPlacement()
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = modifier.then(draggingModifier),
|
||||
propagateMinConstraints = true,
|
||||
) {
|
||||
content(dragging, overing)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package com.pixelized.rplexicon.utilitary.lazyGridDragAndDrop
|
||||
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
|
||||
import androidx.compose.foundation.gestures.drag
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||
import androidx.compose.ui.input.pointer.PointerInputScope
|
||||
import androidx.compose.ui.input.pointer.changedToUp
|
||||
import androidx.compose.ui.input.pointer.positionChange
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
suspend fun PointerInputScope.detectDragGesturesAfterLongPress(
|
||||
onDragStart: (Offset) -> Unit = { },
|
||||
onDragEnd: (Offset) -> Unit = { },
|
||||
onDragCancel: () -> Unit = { },
|
||||
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
|
||||
) {
|
||||
awaitEachGesture {
|
||||
try {
|
||||
// await for a press
|
||||
val down = awaitFirstDown(requireUnconsumed = false)
|
||||
// await for a long press or a cancellation
|
||||
val drag = awaitLongPressOrCancellation(pointerId = down.id)
|
||||
// if the user did a long press
|
||||
if (drag != null) {
|
||||
// callback onDragStart with the initial position.
|
||||
onDragStart.invoke(drag.position)
|
||||
var lastDragInput: PointerInputChange = drag
|
||||
val dragEnded = drag(
|
||||
pointerId = drag.id,
|
||||
onDrag = { input ->
|
||||
lastDragInput = input
|
||||
onDrag(input, input.positionChange())
|
||||
input.consume()
|
||||
}
|
||||
)
|
||||
if (dragEnded) {
|
||||
// consume up if we quit drag gracefully with the up
|
||||
currentEvent.changes.fastForEach {
|
||||
if (it.changedToUp()) it.consume()
|
||||
}
|
||||
onDragEnd(lastDragInput.position)
|
||||
} else {
|
||||
onDragCancel()
|
||||
}
|
||||
}
|
||||
} catch (c: CancellationException) {
|
||||
onDragCancel()
|
||||
throw c
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue