Change the map detail design
This commit is contained in:
parent
e74bd7c097
commit
d09b16ee33
9 changed files with 279 additions and 119 deletions
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.pixelized.rplexicon.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Handle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
width: Dp = MaterialTheme.lexicon.dimens.handle.width,
|
||||||
|
thickness: Dp = MaterialTheme.lexicon.dimens.handle.thickness,
|
||||||
|
color: Color = MaterialTheme.lexicon.colorScheme.handle,
|
||||||
|
shape: Shape = CircleShape
|
||||||
|
) = Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(width = width, height = thickness)
|
||||||
|
.background(
|
||||||
|
color = color,
|
||||||
|
shape = shape,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -38,7 +38,7 @@ fun FantasyMap(
|
||||||
@DrawableRes previewPlaceholder: Int,
|
@DrawableRes previewPlaceholder: Int,
|
||||||
item: State<LocationDetailUio>,
|
item: State<LocationDetailUio>,
|
||||||
selectedItem: State<Int>,
|
selectedItem: State<Int>,
|
||||||
onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
) {
|
) {
|
||||||
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
|
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
|
||||||
val animatedOffset = animateOffsetAsState(targetValue = state.offset, label = "OffsetAnimation")
|
val animatedOffset = animateOffsetAsState(targetValue = state.offset, label = "OffsetAnimation")
|
||||||
|
|
@ -52,14 +52,19 @@ fun FantasyMap(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.pointerInput("DetectTransformGestures") {
|
.pointerInput("DetectTransformGestures") {
|
||||||
detectTransformGestures(panZoomLock = true) { _, pan, zoom, _ ->
|
detectTransformGestures(panZoomLock = true) { _, pan, zoom, _ ->
|
||||||
|
val newScale = state.scale * zoom
|
||||||
|
val oldScale = state.scale
|
||||||
|
val oldOffset = state.offset
|
||||||
|
|
||||||
state.scale(
|
state.scale(
|
||||||
scale = state.scale * zoom,
|
scale = newScale
|
||||||
)
|
)
|
||||||
|
|
||||||
state.pan(
|
state.pan(
|
||||||
offset = Offset(
|
offset = Offset(
|
||||||
x = state.offset.x + pan.x,
|
x = oldOffset.x * newScale / oldScale + pan.x,
|
||||||
y = state.offset.y + pan.y,
|
y = oldOffset.y * newScale / oldScale + pan.y,
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +104,7 @@ fun FantasyMap(
|
||||||
val marquee = item.value.marquees
|
val marquee = item.value.marquees
|
||||||
.asReversed()
|
.asReversed()
|
||||||
.firstOrNull { item ->
|
.firstOrNull { item ->
|
||||||
val radius = 24.dp.toPx() * animatedScale.value
|
val radius = 24.dp.toPx() / animatedScale.value
|
||||||
(size.width * item.position.x).let { tap.x in (it - radius)..(it + radius) } &&
|
(size.width * item.position.x).let { tap.x in (it - radius)..(it + radius) } &&
|
||||||
(size.height * item.position.y).let { tap.y in (it - radius)..(it + radius) }
|
(size.height * item.position.y).let { tap.y in (it - radius)..(it + radius) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,23 @@ package com.pixelized.rplexicon.ui.screens.location.detail
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.gestures.scrollBy
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
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.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -34,21 +41,24 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.Preview
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.composable.Handle
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.skydoves.landscapist.ImageOptions
|
import com.skydoves.landscapist.ImageOptions
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -56,14 +66,7 @@ data class LocationDetailUio(
|
||||||
val name: String,
|
val name: String,
|
||||||
val map: Uri,
|
val map: Uri,
|
||||||
val marquees: List<MarqueeUio>,
|
val marquees: List<MarqueeUio>,
|
||||||
) {
|
)
|
||||||
@Stable
|
|
||||||
data class MarqueeUio(
|
|
||||||
val name: String,
|
|
||||||
val position: Offset,
|
|
||||||
val description: String?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -71,15 +74,35 @@ fun LocationDetail(
|
||||||
viewModel: LocationDetailViewModel = hiltViewModel()
|
viewModel: LocationDetailViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val scroll = rememberScrollState()
|
||||||
val pager = rememberPagerState()
|
val pager = rememberPagerState()
|
||||||
val fantasy = rememberFantasyMapState()
|
val fantasy = rememberFantasyMapState()
|
||||||
val screen = LocalScreenNavHost.current
|
val screen = LocalScreenNavHost.current
|
||||||
|
|
||||||
|
val connection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource
|
||||||
|
): Offset = runBlocking {
|
||||||
|
Offset(
|
||||||
|
x = 0f,
|
||||||
|
y = when (scroll.canScrollForward) {
|
||||||
|
true -> -scroll.scrollBy(-available.y)
|
||||||
|
else -> 0f
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val selectedIndex = remember { mutableStateOf(0) }
|
val selectedIndex = remember { mutableStateOf(0) }
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
LocationContent(
|
LocationContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
connection = connection,
|
||||||
|
scrollState = scroll,
|
||||||
pagerState = pager,
|
pagerState = pager,
|
||||||
fantasyMapState = fantasy,
|
fantasyMapState = fantasy,
|
||||||
item = viewModel.location,
|
item = viewModel.location,
|
||||||
|
|
@ -99,12 +122,32 @@ fun LocationDetail(
|
||||||
fantasy.pan(offset = Offset.Zero)
|
fantasy.pan(offset = Offset.Zero)
|
||||||
},
|
},
|
||||||
onZoomIn = {
|
onZoomIn = {
|
||||||
fantasy.scale(fantasy.scale + 1)
|
val newScale = fantasy.scale + 1
|
||||||
fantasy.pan(offset = fantasy.offset)
|
if (newScale <= fantasy.maxScale) {
|
||||||
|
val oldScale = fantasy.scale
|
||||||
|
val oldOffset = fantasy.offset
|
||||||
|
fantasy.scale(newScale)
|
||||||
|
fantasy.pan(
|
||||||
|
offset = Offset(
|
||||||
|
x = oldOffset.x * newScale / oldScale,
|
||||||
|
y = oldOffset.y * newScale / oldScale,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onZoomOut = {
|
onZoomOut = {
|
||||||
fantasy.scale(fantasy.scale - 1)
|
val newScale = fantasy.scale - 1
|
||||||
fantasy.pan(offset = fantasy.offset)
|
if (newScale >= fantasy.minScale) {
|
||||||
|
val oldScale = fantasy.scale
|
||||||
|
val oldOffset = fantasy.offset
|
||||||
|
fantasy.scale(newScale)
|
||||||
|
fantasy.pan(
|
||||||
|
offset = Offset(
|
||||||
|
x = oldOffset.x * newScale / oldScale,
|
||||||
|
y = oldOffset.y * newScale / oldScale,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -125,19 +168,20 @@ fun LocationDetail(
|
||||||
@Composable
|
@Composable
|
||||||
private fun LocationContent(
|
private fun LocationContent(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
connection: NestedScrollConnection,
|
||||||
|
scrollState: ScrollState,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
fantasyMapState: FantasyMapState,
|
fantasyMapState: FantasyMapState,
|
||||||
item: State<LocationDetailUio>,
|
item: State<LocationDetailUio>,
|
||||||
selectedIndex: State<Int>,
|
selectedIndex: State<Int>,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
onCenter: () -> Unit,
|
onCenter: () -> Unit,
|
||||||
onZoomIn: () -> Unit,
|
onZoomIn: () -> Unit,
|
||||||
onZoomOut: () -> Unit,
|
onZoomOut: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
containerColor = Color.Transparent,
|
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
|
|
@ -149,117 +193,110 @@ private fun LocationContent(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(id = R.string.detail_title))
|
Text(text = stringResource(id = R.string.map_title))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
BoxWithConstraints(
|
||||||
modifier = Modifier.padding(paddingValues = paddingValues),
|
modifier = Modifier.padding(paddingValues = paddingValues),
|
||||||
) {
|
) constraint@{
|
||||||
Surface(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.verticalScroll(state = scrollState),
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(weight = 2f),
|
|
||||||
tonalElevation = 2.dp,
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.clip(shape = RectangleShape),
|
.heightIn(
|
||||||
|
min = this@constraint.maxHeight / 2,
|
||||||
|
max = this@constraint.maxHeight * 2 / 3,
|
||||||
|
),
|
||||||
|
tonalElevation = 2.dp,
|
||||||
) {
|
) {
|
||||||
FantasyMap(
|
Box(
|
||||||
modifier = Modifier.align(alignment = Alignment.Center),
|
modifier = Modifier.clip(shape = RectangleShape),
|
||||||
state = fantasyMapState,
|
|
||||||
previewPlaceholder = R.drawable.im_brulkhai,
|
|
||||||
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
|
||||||
item = item,
|
|
||||||
selectedItem = selectedIndex,
|
|
||||||
onMarquee = onMarquee,
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(alignment = Alignment.BottomEnd)
|
|
||||||
.padding(all = 16.dp),
|
|
||||||
) {
|
) {
|
||||||
val colors = IconButtonDefaults.filledIconButtonColors(
|
FantasyMap(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
modifier = Modifier
|
||||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
.align(alignment = Alignment.Center)
|
||||||
|
.offset { IntOffset(x = 0, y = scrollState.value / 2) },
|
||||||
|
state = fantasyMapState,
|
||||||
|
previewPlaceholder = R.drawable.im_brulkhai,
|
||||||
|
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
||||||
|
item = item,
|
||||||
|
selectedItem = selectedIndex,
|
||||||
|
onMarquee = onMarquee,
|
||||||
)
|
)
|
||||||
FilledIconButton(
|
Column(
|
||||||
onClick = onZoomOut,
|
modifier = Modifier
|
||||||
colors = colors,
|
.align(alignment = Alignment.BottomEnd)
|
||||||
|
.padding(all = 16.dp),
|
||||||
) {
|
) {
|
||||||
Icon(
|
val colors = IconButtonDefaults.filledIconButtonColors(
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_remove_24),
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
contentDescription = null
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
|
||||||
}
|
|
||||||
FilledIconButton(
|
|
||||||
onClick = onZoomIn,
|
|
||||||
colors = colors,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_add_24),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
FilledIconButton(
|
|
||||||
onClick = onCenter,
|
|
||||||
colors = colors,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_zoom_in_map_24),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
)
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = onZoomOut,
|
||||||
|
colors = colors,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_baseline_remove_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = onZoomIn,
|
||||||
|
colors = colors,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_baseline_add_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = onCenter,
|
||||||
|
colors = colors,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_baseline_zoom_in_map_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
HorizontalPager(
|
Handle(
|
||||||
modifier = Modifier.weight(weight = 1f),
|
modifier = Modifier
|
||||||
state = pagerState,
|
.align(alignment = Alignment.CenterHorizontally)
|
||||||
pageCount = item.value.marquees.size,
|
.padding(top = 16.dp)
|
||||||
contentPadding = PaddingValues(all = 16.dp),
|
)
|
||||||
pageSpacing = 8.dp,
|
|
||||||
) {
|
HorizontalPager(
|
||||||
item.value.marquees.getOrNull(it)?.let { marquee ->
|
modifier = Modifier
|
||||||
Marquee(
|
.fillMaxWidth()
|
||||||
modifier = Modifier.fillMaxSize(),
|
.nestedScroll(connection),
|
||||||
marquee = marquee,
|
state = pagerState,
|
||||||
)
|
verticalAlignment = Alignment.Top,
|
||||||
|
pageCount = item.value.marquees.size,
|
||||||
|
contentPadding = PaddingValues(all = 16.dp),
|
||||||
|
pageSpacing = 16.dp,
|
||||||
|
) {
|
||||||
|
item.value.marquees.getOrNull(it)?.let { marquee ->
|
||||||
|
MarqueeItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(this@constraint.maxHeight - 32.dp),
|
||||||
|
marquee = marquee,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun Marquee(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
marquee: LocationDetailUio.MarqueeUio,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = modifier,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
text = marquee.name,
|
|
||||||
)
|
|
||||||
marquee.description?.let {
|
|
||||||
Text(
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
text = it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun HandlePagerScroll(
|
private fun HandlePagerScroll(
|
||||||
|
|
@ -287,6 +324,8 @@ private fun LocationPreview() {
|
||||||
Surface {
|
Surface {
|
||||||
LocationContent(
|
LocationContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
connection = remember { object : NestedScrollConnection {} },
|
||||||
|
scrollState = rememberScrollState(),
|
||||||
pagerState = rememberPagerState(),
|
pagerState = rememberPagerState(),
|
||||||
fantasyMapState = rememberFantasyMapState(),
|
fantasyMapState = rememberFantasyMapState(),
|
||||||
item = remember {
|
item = remember {
|
||||||
|
|
@ -295,12 +334,12 @@ private fun LocationPreview() {
|
||||||
name = "Daggerfall",
|
name = "Daggerfall",
|
||||||
map = Uri.parse("https://i.pinimg.com/originals/6d/56/cd/6d56cd9358cc94a7077157ea3c1b5842.jpg"),
|
map = Uri.parse("https://i.pinimg.com/originals/6d/56/cd/6d56cd9358cc94a7077157ea3c1b5842.jpg"),
|
||||||
marquees = listOf(
|
marquees = listOf(
|
||||||
LocationDetailUio.MarqueeUio(
|
MarqueeUio(
|
||||||
name = "start",
|
name = "start",
|
||||||
position = Offset.Zero,
|
position = Offset.Zero,
|
||||||
description = "Marquee en haut à gauche."
|
description = "Marquee en haut à gauche."
|
||||||
),
|
),
|
||||||
LocationDetailUio.MarqueeUio(
|
MarqueeUio(
|
||||||
name = "end",
|
name = "end",
|
||||||
position = Offset(1f, 1f),
|
position = Offset(1f, 1f),
|
||||||
description = "Marquee en bas à droite."
|
description = "Marquee en bas à droite."
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class LocationDetailViewModel @Inject constructor(
|
||||||
name = source.name,
|
name = source.name,
|
||||||
map = source.uri,
|
map = source.uri,
|
||||||
marquees = source.marquees.map { marquee ->
|
marquees = source.marquees.map { marquee ->
|
||||||
LocationDetailUio.MarqueeUio(
|
MarqueeUio(
|
||||||
name = marquee.name,
|
name = marquee.name,
|
||||||
position = Offset(x = marquee.x, y = marquee.y),
|
position = Offset(x = marquee.x, y = marquee.y),
|
||||||
description = marquee.description,
|
description = marquee.description,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.location.detail
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
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.unit.dp
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class MarqueeUio(
|
||||||
|
val name: String,
|
||||||
|
val position: Offset,
|
||||||
|
val description: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MarqueeItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
marquee: MarqueeUio,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
text = marquee.name,
|
||||||
|
)
|
||||||
|
marquee.description?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
private fun MarqueeItemPreview() {
|
||||||
|
LexiconTheme {
|
||||||
|
MarqueeItem(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
marquee = MarqueeUio(
|
||||||
|
name = "Name",
|
||||||
|
position = Offset.Zero,
|
||||||
|
description = "description",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ class LexiconColors(
|
||||||
val status: Color,
|
val status: Color,
|
||||||
val navigation: Color,
|
val navigation: Color,
|
||||||
val placeholder: Color,
|
val placeholder: Color,
|
||||||
|
val handle: Color,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -32,6 +33,7 @@ fun darkColorScheme(
|
||||||
status = status,
|
status = status,
|
||||||
navigation = navigation,
|
navigation = navigation,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
|
handle = base.onSurface.copy(alpha = 0.5f),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -50,4 +52,5 @@ fun lightColorScheme(
|
||||||
status = status,
|
status = status,
|
||||||
navigation = navigation,
|
navigation = navigation,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
|
handle = base.onSurface.copy(alpha = 0.5f),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,15 @@ data class LexiconDimens(
|
||||||
val item: Dp,
|
val item: Dp,
|
||||||
val detailPadding: Dp,
|
val detailPadding: Dp,
|
||||||
val itemListPadding: PaddingValues,
|
val itemListPadding: PaddingValues,
|
||||||
)
|
val handle: Handle
|
||||||
|
) {
|
||||||
|
@Stable
|
||||||
|
@Immutable
|
||||||
|
data class Handle(
|
||||||
|
val width: Dp,
|
||||||
|
val thickness: Dp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun lexiconDimen(
|
fun lexiconDimen(
|
||||||
itemHeight: Dp = 52.dp,
|
itemHeight: Dp = 52.dp,
|
||||||
|
|
@ -21,8 +29,13 @@ fun lexiconDimen(
|
||||||
top = 8.dp,
|
top = 8.dp,
|
||||||
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
||||||
),
|
),
|
||||||
|
handle: LexiconDimens.Handle = LexiconDimens.Handle(
|
||||||
|
width = 32.dp,
|
||||||
|
thickness = 4.dp,
|
||||||
|
),
|
||||||
) = LexiconDimens(
|
) = LexiconDimens(
|
||||||
item = itemHeight,
|
item = itemHeight,
|
||||||
detailPadding = detailPadding,
|
detailPadding = detailPadding,
|
||||||
itemListPadding = itemListPadding
|
itemListPadding = itemListPadding,
|
||||||
|
handle = handle,
|
||||||
)
|
)
|
||||||
|
|
@ -52,4 +52,6 @@
|
||||||
<string name="quest_detail_area">Lieu :</string>
|
<string name="quest_detail_area">Lieu :</string>
|
||||||
<string name="quest_detail_individual_reward">Récompense individuelle :</string>
|
<string name="quest_detail_individual_reward">Récompense individuelle :</string>
|
||||||
<string name="quest_detail_group_rewars">Récompense de groupe :</string>
|
<string name="quest_detail_group_rewars">Récompense de groupe :</string>
|
||||||
|
|
||||||
|
<string name="map_title">Carte</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -53,4 +53,5 @@
|
||||||
<string name="quest_detail_individual_reward">Individual reward:</string>
|
<string name="quest_detail_individual_reward">Individual reward:</string>
|
||||||
<string name="quest_detail_group_rewars">Group reward:</string>
|
<string name="quest_detail_group_rewars">Group reward:</string>
|
||||||
|
|
||||||
|
<string name="map_title">Map</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue