New Inventory component.
This commit is contained in:
parent
250d9cbb7c
commit
531e4bea98
25 changed files with 842 additions and 77 deletions
|
|
@ -38,7 +38,7 @@ import com.pixelized.rplexicon.ui.screens.rolls.RollOverlay
|
|||
import com.pixelized.rplexicon.ui.screens.rolls.RollOverlayViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.rememberBlurredRollOverlayHostState
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
val NO_WINDOW_INSETS = WindowInsets(0, 0, 0, 0)
|
||||
|
|
@ -128,7 +128,7 @@ class MainActivity : ComponentActivity() {
|
|||
Snackbar(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -35,7 +34,7 @@ import androidx.compose.ui.window.DialogProperties
|
|||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toLabel
|
||||
|
||||
@Stable
|
||||
|
|
@ -66,7 +65,7 @@ fun HandleHitPointEditDialog(
|
|||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.ddBorder(
|
||||
modifier = Modifier.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SkillEditDialogUio(
|
||||
|
|
@ -48,7 +48,7 @@ fun HandleSkillEditDialog(
|
|||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.ddBorder(
|
||||
modifier = Modifier.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SpellEditDialogUio(
|
||||
|
|
@ -49,7 +49,7 @@ fun HandleSpellEditDialog(
|
|||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.ddBorder(
|
||||
modifier = Modifier.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -27,14 +26,12 @@ 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 androidx.core.net.toUri
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.data.model.Attack
|
||||
import com.pixelized.rplexicon.ui.composable.images.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.FlatValue
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.uri
|
||||
|
||||
@Stable
|
||||
data class AttackUio(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.sizeIn
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
|
|
@ -26,7 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toLabel
|
||||
|
||||
@Stable
|
||||
|
|
@ -88,7 +87,7 @@ private fun StatPreview() {
|
|||
Stat(
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.character.composable.dialogs
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
|
@ -37,7 +36,7 @@ import com.pixelized.rplexicon.ui.composable.images.BackgroundImage
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.annotateWithDropCap
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class AlterationDialogDetailUio(
|
||||
|
|
@ -75,7 +74,7 @@ fun AlterationDetailDialog(
|
|||
enabled = false,
|
||||
onClick = { },
|
||||
)
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import com.pixelized.rplexicon.ui.composable.images.BackgroundImage
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.annotateWithDropCap
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SkillDialogDetailUio(
|
||||
|
|
@ -70,7 +70,7 @@ fun SkillDetailDialog(
|
|||
enabled = false,
|
||||
onClick = { },
|
||||
)
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.dialogs
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -40,7 +39,7 @@ import com.pixelized.rplexicon.ui.composable.images.BackgroundImage
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.annotateWithDropCap
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SpellDialogDetailUio(
|
||||
|
|
@ -83,7 +82,7 @@ fun SpellDetailDialog(
|
|||
enabled = false,
|
||||
onClick = { },
|
||||
)
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory
|
||||
|
||||
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.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.ui.composable.images.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
data class InventoryItemUio(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val amount: Int,
|
||||
val container: Boolean,
|
||||
val icon: Any?,
|
||||
val items: List<InventoryItemUio> = emptyList(),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun InventoryItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: InventoryItemUio,
|
||||
) {
|
||||
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.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.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 > 0 }?.let { "$it" } ?: " ", //
|
||||
)
|
||||
}
|
||||
AnimatedContent(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.TopEnd)
|
||||
.offset(x = 0.dp, y = (-4).dp)
|
||||
.padding(horizontal = 2.dp),
|
||||
targetState = item.items.size,
|
||||
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(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.End,
|
||||
text = amount.takeIf { it > 0 }?.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: InventoryItemUio,
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
InventoryItem(
|
||||
item = preview,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassInventoryItemProvider : PreviewParameterProvider<InventoryItemUio> {
|
||||
override val values: Sequence<InventoryItemUio> = sequenceOf(
|
||||
InventoryItemUio(
|
||||
id = 0,
|
||||
name = "Pouch",
|
||||
amount = 1,
|
||||
container = true,
|
||||
icon = R.drawable.icbg_pouch_a_unfaded,
|
||||
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 1,
|
||||
name = "Scroll of blessing",
|
||||
amount = 1,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_scroll_of_bless_unfaded,
|
||||
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 2,
|
||||
name = "Potion of blessing",
|
||||
amount = 2,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_pot_potion_of_healing_unfaded,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.inventory
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
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.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.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
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
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
fun Pouet() {
|
||||
val items = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
InventoryItemUio(
|
||||
id = 0,
|
||||
name = "Pouch",
|
||||
amount = 1,
|
||||
container = true,
|
||||
icon = R.drawable.icbg_pouch_a_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 1,
|
||||
name = "Scroll of blessing",
|
||||
amount = 1,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_scroll_of_bless_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 2,
|
||||
name = "Scroll of spirit weapon",
|
||||
amount = 1,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_book_signedtradebisa_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 3,
|
||||
name = "Potion of healing",
|
||||
amount = 2,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_potion_of_superior_healing_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 4,
|
||||
name = "Potion of supérior healing",
|
||||
amount = 2,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_pot_potion_of_healing_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 5,
|
||||
name = "Potion of holy water",
|
||||
amount = 2,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_grn_holy_water_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 6,
|
||||
name = "Leather armor",
|
||||
amount = 1,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_leather_armour_rogue_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 7,
|
||||
name = "Silver battleaxe",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_battleaxe_plus_one_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 8,
|
||||
name = "Hand crossbow",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_hand_crossbow_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 9,
|
||||
name = "Goodberry",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_food_goodberry_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 10,
|
||||
name = "Goodberry",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_worg_fang_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 11,
|
||||
name = "Lantern of revealing",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_lantern_of_revealing,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 12,
|
||||
name = "Dust of disappearance",
|
||||
amount = 0,
|
||||
container = false,
|
||||
icon = R.drawable.icbg_haste_spore_grenade_unfaded,
|
||||
),
|
||||
InventoryItemUio(
|
||||
id = 13,
|
||||
name = "Pouch",
|
||||
amount = 1,
|
||||
container = true,
|
||||
icon = R.drawable.icbg_pouch_a_unfaded,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val overIndex = remember { mutableIntStateOf(-1) }
|
||||
val contentPadding = remember { PaddingValues(16.dp) }
|
||||
val gridState = rememberLazyGridState()
|
||||
val dragDropState = rememberGridDragDropState(
|
||||
contentPadding = contentPadding,
|
||||
gridState = gridState,
|
||||
onMove = { fromIndex, toIndex ->
|
||||
items.value = items.value.toMutableList().apply {
|
||||
add(toIndex, removeAt(fromIndex))
|
||||
}
|
||||
},
|
||||
onOver = { _, toIndex ->
|
||||
val receiver = items.value[toIndex]
|
||||
if (receiver.container) {
|
||||
overIndex.intValue = toIndex
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
onDrop = { fromIndex, toIndex ->
|
||||
if (fromIndex != toIndex) {
|
||||
val receiver = items.value[toIndex]
|
||||
if (receiver.container) {
|
||||
items.value = items.value.toMutableList().apply {
|
||||
val item = removeAt(fromIndex)
|
||||
val receiverCopy = receiver.copy(
|
||||
items = receiver.items.toMutableList().also {
|
||||
it.add(item)
|
||||
}
|
||||
)
|
||||
val receiverIndex = indexOf(receiver)
|
||||
removeAt(receiverIndex)
|
||||
add(receiverIndex, receiverCopy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(5),
|
||||
modifier = Modifier
|
||||
.dragContainer(dragDropState)
|
||||
.fillMaxSize(),
|
||||
state = gridState,
|
||||
contentPadding = contentPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = items.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(
|
||||
item = item,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio
|
|||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterSheetStatePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.dialogs.SkillDetailDialog
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
|
|
@ -146,7 +146,7 @@ fun ProficiencyPageContent(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.sizeIn(minWidth = 100.dp, minHeight = 116.dp)
|
||||
.ddBorder(outline = outline, inner = inner)
|
||||
.doubleBorder(outline = outline, inner = inner)
|
||||
.padding(all = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
|
|
@ -163,7 +163,7 @@ fun ProficiencyPageContent(
|
|||
}
|
||||
sheet.stats.forEach {
|
||||
Stat(
|
||||
modifier = Modifier.ddBorder(inner = inner, outline = outline),
|
||||
modifier = Modifier.doubleBorder(inner = inner, outline = outline),
|
||||
stat = it,
|
||||
onClick = onStats,
|
||||
)
|
||||
|
|
@ -266,28 +266,28 @@ private fun ProficiencyLayout(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.layoutId("SavingThrowsId")
|
||||
.ddBorder(inner = inner, outline = outline),
|
||||
.doubleBorder(inner = inner, outline = outline),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
content = savingThrows,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.layoutId("ProficienciesId")
|
||||
.ddBorder(inner = inner, outline = outline),
|
||||
.doubleBorder(inner = inner, outline = outline),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
content = proficiencies,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.layoutId("SpeedId")
|
||||
.ddBorder(inner = inner, outline = outline),
|
||||
.doubleBorder(inner = inner, outline = outline),
|
||||
content = speed,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.layoutId("PassivesId")
|
||||
.ddBorder(inner = inner, outline = outline),
|
||||
.doubleBorder(inner = inner, outline = outline),
|
||||
content = passives,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import com.pixelized.rplexicon.isInDarkTheme
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import com.pixelized.rplexicon.utilitary.highlightRegex
|
||||
import java.util.UUID
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ fun ThrowsCard(
|
|||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = inner,
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberAttributesSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class AttributesSummaryUio(
|
||||
|
|
@ -45,7 +45,7 @@ fun AttributesSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberCharacteristicsSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class CharacteristicsSummaryUio(
|
||||
|
|
@ -48,7 +48,7 @@ fun CharacteristicsSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberPassivesSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class PassivesSummaryUio(
|
||||
|
|
@ -43,7 +43,7 @@ fun PassivesSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberProficienciesSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class ProficiencySummaryUio(
|
||||
|
|
@ -58,7 +58,7 @@ fun ProficiencySummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberSavingThrowsSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SavingThrowsSummaryUio(
|
||||
|
|
@ -46,7 +46,7 @@ fun SavingThrowsSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
|
||||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberSpellsSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
|
||||
@Stable
|
||||
data class SpellSummaryUio(
|
||||
|
|
@ -53,7 +53,7 @@ fun SpellSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import com.pixelized.rplexicon.R
|
|||
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberStatusSummary
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.verticalDivider
|
||||
|
||||
@Stable
|
||||
|
|
@ -80,7 +80,7 @@ fun StatusSummary(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.ddBorder(
|
||||
.doubleBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ fun lightColorScheme(
|
|||
secondary = BaseColorPalette.Teal80,
|
||||
tertiary = BaseColorPalette.Teal40,
|
||||
onPrimary = Color.White,
|
||||
surfaceTint = BaseColorPalette.Purple40,
|
||||
surfaceTint = Color.Gray,
|
||||
),
|
||||
placeholder: Color = Color(red = 230, green = 225, blue = 229),
|
||||
sheet: LexiconColors.CharacterSheet = LexiconColors.CharacterSheet(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
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,
|
||||
onOver: (Int, Int) -> Boolean,
|
||||
onDrop: (Int, Int) -> Unit,
|
||||
): GridDragDropState {
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val currentOnMove = rememberUpdatedState(newValue = onMove)
|
||||
val currentOnOver = rememberUpdatedState(newValue = onOver)
|
||||
val currentOnDrop = rememberUpdatedState(newValue = onDrop)
|
||||
|
||||
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,
|
||||
onOver = currentOnOver.value,
|
||||
onDrop = currentOnDrop.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 onOver: (Int, Int) -> Boolean,
|
||||
private val onDrop: (Int, Int) -> Unit,
|
||||
) {
|
||||
internal val scrollChannel = Channel<Float>()
|
||||
|
||||
var draggingItemIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
var overItemIndex by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
private var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
|
||||
|
||||
private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
|
||||
|
||||
internal val draggingItemOffset: Offset
|
||||
get() = draggingItemLayoutInfo
|
||||
?.let { item -> draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset() }
|
||||
?: Offset.Zero
|
||||
|
||||
private val draggingItemLayoutInfo: LazyGridItemInfo?
|
||||
get() = state.layoutInfo.visibleItemsInfo
|
||||
.firstOrNull { it.index == draggingItemIndex }
|
||||
|
||||
internal var previousIndexOfDraggedItem by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
internal var previousItemOffset = Animatable(Offset.Zero, 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 onDragInterrupted(offset: Offset) {
|
||||
val localDraggingItemIndex = draggingItemIndex
|
||||
if (localDraggingItemIndex != null) {
|
||||
state.layoutInfo.visibleItemsInfo
|
||||
.firstItemWithOffsetOrNull(offset)
|
||||
?.let { onDrop(localDraggingItemIndex, it.index) }
|
||||
|
||||
previousIndexOfDraggedItem = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
previousItemOffset.snapTo(startOffset)
|
||||
previousItemOffset.animateTo(
|
||||
Offset.Zero,
|
||||
spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = Offset.VisibilityThreshold
|
||||
)
|
||||
)
|
||||
previousIndexOfDraggedItem = null
|
||||
}
|
||||
}
|
||||
overItemIndex = null
|
||||
draggingItemDraggedDelta = Offset.Zero
|
||||
draggingItemIndex = null
|
||||
draggingItemInitialOffset = Offset.Zero
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
overItemIndex = targetItem?.index
|
||||
|
||||
if (targetItem != null) {
|
||||
if (onOver.invoke(draggingItem.index, targetItem.index)) {
|
||||
onMove.invoke(draggingItem.index, targetItem.index)
|
||||
draggingItemIndex = targetItem.index
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.previousIndexOfDraggedItem -> {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer {
|
||||
translationX = dragDropState.previousItemOffset.value.x
|
||||
translationY = dragDropState.previousItemOffset.value.y
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Modifier.animateItemPlacement()
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = modifier.then(draggingModifier),
|
||||
propagateMinConstraints = true,
|
||||
) {
|
||||
content(dragging, overing)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -106,7 +105,7 @@ fun Modifier.clickableInterceptor(): Modifier = composed {
|
|||
)
|
||||
}
|
||||
|
||||
fun Modifier.ddBorder(
|
||||
fun Modifier.doubleBorder(
|
||||
horizontalSpacing: Dp = 3.dp,
|
||||
verticalSpacing: Dp = 3.dp,
|
||||
outline: Shape,
|
||||
|
|
@ -114,37 +113,38 @@ fun Modifier.ddBorder(
|
|||
inner: Shape,
|
||||
innerWidth: Dp = 1.dp,
|
||||
): Modifier = composed {
|
||||
val isDarkTheme = isInDarkTheme()
|
||||
val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } }
|
||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||
this then Modifier
|
||||
.border(
|
||||
width = outlineWidth,
|
||||
color = colorScheme.characterSheet.outlineBorder,
|
||||
shape = outline,
|
||||
)
|
||||
.background(
|
||||
shape = outline,
|
||||
color = colorScheme.base.surfaceColorAtElevation(elevation.value)
|
||||
)
|
||||
.padding(
|
||||
horizontal = horizontalSpacing,
|
||||
vertical = verticalSpacing,
|
||||
)
|
||||
.border(
|
||||
width = innerWidth,
|
||||
color = colorScheme.characterSheet.innerBorder,
|
||||
shape = inner,
|
||||
)
|
||||
.background(
|
||||
shape = inner,
|
||||
color = colorScheme.base.surfaceColorAtElevation(elevation.value)
|
||||
)
|
||||
.clip(
|
||||
shape = inner,
|
||||
)
|
||||
this then Modifier.doubleBorder(
|
||||
horizontalSpacing = horizontalSpacing,
|
||||
verticalSpacing = verticalSpacing,
|
||||
backgroundColor = colorScheme.base.surfaceColorAtElevation(1.dp),
|
||||
outline = outline,
|
||||
outlineWidth = outlineWidth,
|
||||
outlineColor = colorScheme.characterSheet.outlineBorder,
|
||||
inner = inner,
|
||||
innerWidth = innerWidth,
|
||||
innerColor = colorScheme.characterSheet.innerBorder,
|
||||
)
|
||||
}
|
||||
|
||||
fun Modifier.doubleBorder(
|
||||
horizontalSpacing: Dp = 3.dp,
|
||||
verticalSpacing: Dp = 3.dp,
|
||||
backgroundColor: Color,
|
||||
outline: Shape,
|
||||
outlineWidth: Dp = 1.dp,
|
||||
outlineColor: Color,
|
||||
inner: Shape,
|
||||
innerWidth: Dp = 1.dp,
|
||||
innerColor: Color,
|
||||
): Modifier = this then Modifier
|
||||
.border(width = outlineWidth, color = outlineColor, shape = outline)
|
||||
.background(shape = outline, color = backgroundColor)
|
||||
.padding(horizontal = horizontalSpacing, vertical = verticalSpacing)
|
||||
.border(width = innerWidth, color = innerColor, shape = inner)
|
||||
.background(shape = inner, color = backgroundColor)
|
||||
.clip(shape = inner)
|
||||
|
||||
fun Modifier.lexiconShadow(): Modifier {
|
||||
return this then composed {
|
||||
val isDarkTheme = isInDarkTheme()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable/icbg_pouch_a_unfaded.webp
Normal file
BIN
app/src/main/res/drawable/icbg_pouch_a_unfaded.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Loading…
Add table
Add a link
Reference in a new issue