Remove old drag&drop code.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-08-20 14:54:32 +02:00
parent ac71765c44
commit b27149edaf
12 changed files with 18 additions and 1324 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -74,6 +74,7 @@ class ItemListViewModel @Inject constructor(
}
fun show() {
filter.value = ""
_display.value = true
}

View file

@ -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,
// )
// }
}
}

View file

@ -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" } ?: " ", // &nbsp;
)
}
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" } ?: " ", // &nbsp;
)
}
}
}
@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,
),
)
}

View file

@ -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 = { _, _ -> }
)
}
}
}

View file

@ -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,
// )
// }
}
}

View file

@ -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)
}
}

View file

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