Add some sugar on the location feature
This commit is contained in:
parent
b6af245327
commit
85cdd69570
2 changed files with 149 additions and 28 deletions
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
|
@ -29,8 +30,14 @@ import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||||
|
import com.pixelized.rplexicon.ui.theme.colors.LexiconColors
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
import com.skydoves.landscapist.ImageOptions
|
import com.skydoves.landscapist.ImageOptions
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
|
private val RADIUS = 12.dp
|
||||||
|
private val SQUARE = (RADIUS / sqrt(2f))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FantasyMap(
|
fun FantasyMap(
|
||||||
|
|
@ -39,13 +46,30 @@ fun FantasyMap(
|
||||||
imageOptions: ImageOptions = ImageOptions(),
|
imageOptions: ImageOptions = ImageOptions(),
|
||||||
@DrawableRes previewPlaceholder: Int,
|
@DrawableRes previewPlaceholder: Int,
|
||||||
item: State<LocationDetailUio>,
|
item: State<LocationDetailUio>,
|
||||||
|
highlight: State<Offset>,
|
||||||
selectedItem: State<Int>,
|
selectedItem: State<Int>,
|
||||||
onMarquee: (MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
onTap: (Offset) -> Unit,
|
onTap: (Offset) -> Unit,
|
||||||
) {
|
) {
|
||||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||||
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
|
|
||||||
val animatedOffset = animateOffsetAsState(targetValue = state.offset, label = "OffsetAnimation")
|
val animatedScale = animateFloatAsState(
|
||||||
|
targetValue = state.scale,
|
||||||
|
label = "ScaleAnimation",
|
||||||
|
)
|
||||||
|
val animatedOffset = animateOffsetAsState(
|
||||||
|
targetValue = state.offset,
|
||||||
|
label = "OffsetAnimation",
|
||||||
|
)
|
||||||
|
val animatedMarqueeAlpha = animateFloatAsState(
|
||||||
|
targetValue = if (state.freeHand) 0f else 1f,
|
||||||
|
label = "MarqueAlphaAnimation",
|
||||||
|
)
|
||||||
|
val animatedCrossAlpha = remember {
|
||||||
|
derivedStateOf {
|
||||||
|
1f - animatedMarqueeAlpha.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = "CenterOnMarquee:${selectedItem.value}") {
|
LaunchedEffect(key1 = "CenterOnMarquee:${selectedItem.value}") {
|
||||||
item.value.marquees.getOrNull(selectedItem.value)?.position
|
item.value.marquees.getOrNull(selectedItem.value)?.position
|
||||||
|
|
@ -95,37 +119,27 @@ fun FantasyMap(
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
drawContent()
|
drawContent()
|
||||||
|
|
||||||
if (state.freeHand.not()) {
|
if (animatedMarqueeAlpha.value > 0f) {
|
||||||
item.value.marquees.forEachIndexed { index, item ->
|
item.value.marquees.forEachIndexed { index, item ->
|
||||||
if (item.position != Offset.Unspecified) {
|
if (item.position != Offset.Unspecified) {
|
||||||
drawCircle(
|
drawMarque(
|
||||||
color = colorScheme.shadow,
|
colorScheme = colorScheme,
|
||||||
radius = 12.dp.toPx() / animatedScale.value,
|
alpha = animatedMarqueeAlpha,
|
||||||
style = Stroke(
|
scale = animatedScale,
|
||||||
width = 2.dp.toPx() / animatedScale.value,
|
position = item.position,
|
||||||
),
|
selected = selectedItem.value == index
|
||||||
center = Offset(
|
|
||||||
x = size.width * item.position.x,
|
|
||||||
y = size.height * item.position.y + 2.dp.toPx() / animatedScale.value,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
drawCircle(
|
|
||||||
color = when (selectedItem.value) {
|
|
||||||
index -> colorScheme.base.primary
|
|
||||||
else -> Color.White
|
|
||||||
},
|
|
||||||
radius = 12.dp.toPx() / animatedScale.value,
|
|
||||||
style = Stroke(
|
|
||||||
width = 2.dp.toPx() / animatedScale.value,
|
|
||||||
),
|
|
||||||
center = Offset(
|
|
||||||
x = size.width * item.position.x,
|
|
||||||
y = size.height * item.position.y,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (highlight.value != Offset.Unspecified && animatedCrossAlpha.value > 0f) {
|
||||||
|
drawCross(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
alpha = animatedCrossAlpha,
|
||||||
|
scale = animatedScale,
|
||||||
|
position = highlight.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.pointerInput("DetectTapGestures") {
|
.pointerInput("DetectTapGestures") {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
|
|
@ -240,4 +254,100 @@ class FantasyMapState(
|
||||||
else -> origin
|
else -> origin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DrawScope.drawMarque(
|
||||||
|
colorScheme: LexiconColors,
|
||||||
|
alpha: State<Float>,
|
||||||
|
scale: State<Float>,
|
||||||
|
position: Offset,
|
||||||
|
selected: Boolean,
|
||||||
|
) {
|
||||||
|
drawCircle(
|
||||||
|
color = colorScheme.shadow,
|
||||||
|
alpha = alpha.value,
|
||||||
|
radius = 12.dp.toPx() / scale.value,
|
||||||
|
style = Stroke(
|
||||||
|
width = 2.dp.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
center = Offset(
|
||||||
|
x = size.width * position.x,
|
||||||
|
y = size.height * position.y + 2.dp.toPx() / scale.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawCircle(
|
||||||
|
color = when (selected) {
|
||||||
|
true -> colorScheme.base.primary
|
||||||
|
else -> Color.White
|
||||||
|
},
|
||||||
|
alpha = alpha.value,
|
||||||
|
radius = 12.dp.toPx() / scale.value,
|
||||||
|
style = Stroke(
|
||||||
|
width = 2.dp.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
center = Offset(
|
||||||
|
x = size.width * position.x,
|
||||||
|
y = size.height * position.y,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DrawScope.drawCross(
|
||||||
|
colorScheme: LexiconColors,
|
||||||
|
alpha: State<Float>,
|
||||||
|
scale: State<Float>,
|
||||||
|
position: Offset,
|
||||||
|
) {
|
||||||
|
drawLine(
|
||||||
|
color = colorScheme.shadow,
|
||||||
|
alpha = alpha.value,
|
||||||
|
strokeWidth = 2.dp.toPx() / scale.value,
|
||||||
|
start = Offset(
|
||||||
|
x = size.width * position.x - SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y - SQUARE.toPx() / scale.value + 2.dp.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = size.width * position.x + SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y + SQUARE.toPx() / scale.value + 2.dp.toPx() / scale.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawLine(
|
||||||
|
color = colorScheme.shadow,
|
||||||
|
alpha = alpha.value,
|
||||||
|
strokeWidth = 2.dp.toPx() / scale.value,
|
||||||
|
start = Offset(
|
||||||
|
x = size.width * position.x + SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y - SQUARE.toPx() / scale.value + 2.dp.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = size.width * position.x - SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y + SQUARE.toPx() / scale.value + 2.dp.toPx() / scale.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawLine(
|
||||||
|
color = colorScheme.base.primary,
|
||||||
|
alpha = alpha.value,
|
||||||
|
strokeWidth = 2.dp.toPx() / scale.value,
|
||||||
|
start = Offset(
|
||||||
|
x = size.width * position.x - SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y - SQUARE.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = size.width * position.x + SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y + SQUARE.toPx() / scale.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawLine(
|
||||||
|
color = colorScheme.base.primary,
|
||||||
|
alpha = alpha.value,
|
||||||
|
strokeWidth = 2.dp.toPx() / scale.value,
|
||||||
|
start = Offset(
|
||||||
|
x = size.width * position.x + SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y - SQUARE.toPx() / scale.value,
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = size.width * position.x - SQUARE.toPx() / scale.value,
|
||||||
|
y = size.height * position.y + SQUARE.toPx() / scale.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,6 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledIconButton
|
import androidx.compose.material3.FilledIconButton
|
||||||
import androidx.compose.material3.FilledIconToggleButton
|
import androidx.compose.material3.FilledIconToggleButton
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
|
|
@ -108,6 +107,7 @@ fun LocationDetail(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mapHighlight = remember { mutableStateOf(Offset.Unspecified) }
|
||||||
val selectedIndex = remember { mutableStateOf(0) }
|
val selectedIndex = remember { mutableStateOf(0) }
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
|
|
@ -119,6 +119,7 @@ fun LocationDetail(
|
||||||
fantasyMapState = fantasy,
|
fantasyMapState = fantasy,
|
||||||
item = viewModel.location,
|
item = viewModel.location,
|
||||||
selectedIndex = selectedIndex,
|
selectedIndex = selectedIndex,
|
||||||
|
mapHighlight = mapHighlight,
|
||||||
onBack = {
|
onBack = {
|
||||||
screen.popBackStack()
|
screen.popBackStack()
|
||||||
},
|
},
|
||||||
|
|
@ -137,9 +138,16 @@ fun LocationDetail(
|
||||||
actionLabel = ok,
|
actionLabel = ok,
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
|
mapHighlight.value = Offset.Unspecified
|
||||||
}
|
}
|
||||||
|
mapHighlight.value = it
|
||||||
},
|
},
|
||||||
onTouch = {
|
onTouch = {
|
||||||
|
if (it) {
|
||||||
|
mapHighlight.value = Offset.Unspecified
|
||||||
|
} else {
|
||||||
|
job.value?.cancel()
|
||||||
|
}
|
||||||
fantasy.toggleFreeHand(it)
|
fantasy.toggleFreeHand(it)
|
||||||
},
|
},
|
||||||
onCenter = {
|
onCenter = {
|
||||||
|
|
@ -199,6 +207,7 @@ private fun LocationContent(
|
||||||
fantasyMapState: FantasyMapState,
|
fantasyMapState: FantasyMapState,
|
||||||
item: State<LocationDetailUio>,
|
item: State<LocationDetailUio>,
|
||||||
selectedIndex: State<Int>,
|
selectedIndex: State<Int>,
|
||||||
|
mapHighlight: State<Offset>,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onMarquee: (MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
onMapTap: (Offset) -> Unit,
|
onMapTap: (Offset) -> Unit,
|
||||||
|
|
@ -266,6 +275,7 @@ private fun LocationContent(
|
||||||
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
||||||
item = item,
|
item = item,
|
||||||
selectedItem = selectedIndex,
|
selectedItem = selectedIndex,
|
||||||
|
highlight = mapHighlight,
|
||||||
onMarquee = onMarquee,
|
onMarquee = onMarquee,
|
||||||
onTap = onMapTap,
|
onTap = onMapTap,
|
||||||
)
|
)
|
||||||
|
|
@ -412,6 +422,7 @@ private fun LocationPreview() {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selectedIndex = remember { mutableStateOf(0) },
|
selectedIndex = remember { mutableStateOf(0) },
|
||||||
|
mapHighlight = remember { mutableStateOf(Offset(0.5f, 0.5f)) },
|
||||||
onBack = { },
|
onBack = { },
|
||||||
onMarquee = { },
|
onMarquee = { },
|
||||||
onMapTap = { },
|
onMapTap = { },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue