Styling up the LocationDetail
This commit is contained in:
parent
e3cd0bdd4b
commit
b9d14d12ff
11 changed files with 156 additions and 55 deletions
|
|
@ -100,7 +100,7 @@ fun LexiconItem(
|
|||
true -> emptyList()
|
||||
else -> listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.dropCapMediumSpan,
|
||||
item = typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = 1,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import androidx.compose.ui.unit.IntSize
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.ui.theme.colors.LexiconColors
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
|
||||
|
|
@ -39,12 +38,13 @@ import com.skydoves.landscapist.ImageOptions
|
|||
fun FantasyMap(
|
||||
modifier: Modifier = Modifier,
|
||||
state: FantasyMapState,
|
||||
model: () -> Any?,
|
||||
imageOptions: ImageOptions = ImageOptions(),
|
||||
@DrawableRes previewPlaceholder: Int,
|
||||
item: State<LocationDetailUio>,
|
||||
items: State<List<AnnotatedMarqueeUio>>,
|
||||
highlight: State<Offset>,
|
||||
selectedItem: State<Int>,
|
||||
onMarquee: (MarqueeUio) -> Unit,
|
||||
onMarquee: (AnnotatedMarqueeUio) -> Unit,
|
||||
onTap: (Offset) -> Unit,
|
||||
) {
|
||||
val lexiconTheme = MaterialTheme.lexicon
|
||||
|
|
@ -68,7 +68,7 @@ fun FantasyMap(
|
|||
}
|
||||
|
||||
LaunchedEffect(key1 = "CenterOnMarquee:${selectedItem.value}") {
|
||||
item.value.marquees.getOrNull(selectedItem.value)?.position
|
||||
items.value.getOrNull(selectedItem.value)?.position
|
||||
?.let { state.pan(state.computeMarqueeOffset(it)) }
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ fun FantasyMap(
|
|||
.drawWithContent {
|
||||
drawContent()
|
||||
if (animatedMarqueeAlpha.value > 0f) {
|
||||
item.value.marquees.forEachIndexed { index, item ->
|
||||
items.value.forEachIndexed { index, item ->
|
||||
if (item.position != Offset.Unspecified) {
|
||||
drawMarque(
|
||||
theme = lexiconTheme,
|
||||
|
|
@ -147,7 +147,7 @@ fun FantasyMap(
|
|||
)
|
||||
)
|
||||
} else {
|
||||
val marquee = item.value.marquees
|
||||
val marquee = items.value
|
||||
.asReversed()
|
||||
.firstOrNull { item ->
|
||||
if (item.position != Offset.Unspecified) {
|
||||
|
|
@ -163,7 +163,7 @@ fun FantasyMap(
|
|||
}
|
||||
)
|
||||
},
|
||||
imageModel = { item.value.map },
|
||||
imageModel = model,
|
||||
imageOptions = imageOptions,
|
||||
previewPlaceholder = previewPlaceholder,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.location.detail
|
|||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -36,6 +37,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
|
|
@ -43,13 +45,18 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
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.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
|
|
@ -62,7 +69,6 @@ import com.pixelized.rplexicon.ui.composable.Handle
|
|||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.rememberTextSize
|
||||
import com.skydoves.landscapist.ImageOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -77,6 +83,13 @@ data class LocationDetailUio(
|
|||
val marquees: List<MarqueeUio>,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class AnnotatedLocationDetailUio(
|
||||
val name: AnnotatedString,
|
||||
val map: Uri,
|
||||
val marquees: List<AnnotatedMarqueeUio>,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LocationDetail(
|
||||
|
|
@ -94,6 +107,7 @@ fun LocationDetail(
|
|||
|
||||
val ok = stringResource(id = android.R.string.ok)
|
||||
|
||||
val location = rememberAnnotation(item = viewModel.location)
|
||||
val snackJob = remember { mutableStateOf<Job?>(null) }
|
||||
val mapHighlight = remember { mutableStateOf(Offset.Unspecified) }
|
||||
val selectedIndex = remember { mutableStateOf(0) }
|
||||
|
|
@ -107,7 +121,7 @@ fun LocationDetail(
|
|||
scrollState = scroll,
|
||||
pagerState = pager,
|
||||
fantasyMapState = fantasy,
|
||||
item = viewModel.location,
|
||||
item = location,
|
||||
selectedIndex = selectedIndex,
|
||||
mapHighlight = mapHighlight,
|
||||
onBack = {
|
||||
|
|
@ -115,7 +129,7 @@ fun LocationDetail(
|
|||
},
|
||||
onMarquee = {
|
||||
scope.launch {
|
||||
val index = max(viewModel.location.value.marquees.indexOf(it), 0)
|
||||
val index = max(location.value.marquees.indexOf(it), 0)
|
||||
selectedIndex.value = index
|
||||
pager.animateScrollToPage(page = index)
|
||||
}
|
||||
|
|
@ -195,18 +209,19 @@ private fun LocationContent(
|
|||
scrollState: ScrollState,
|
||||
pagerState: PagerState,
|
||||
fantasyMapState: FantasyMapState,
|
||||
item: State<LocationDetailUio>,
|
||||
item: State<AnnotatedLocationDetailUio>,
|
||||
selectedIndex: State<Int>,
|
||||
mapHighlight: State<Offset>,
|
||||
onBack: () -> Unit,
|
||||
onMarquee: (MarqueeUio) -> Unit,
|
||||
onMarquee: (AnnotatedMarqueeUio) -> Unit,
|
||||
onMapTap: (Offset) -> Unit,
|
||||
onTouch: (Boolean) -> Unit,
|
||||
onCenter: () -> Unit,
|
||||
onZoomIn: () -> Unit,
|
||||
onZoomOut: () -> Unit,
|
||||
) {
|
||||
val itemNameSize = rememberTextSize(style = MaterialTheme.typography.headlineSmall)
|
||||
val density = LocalDensity.current
|
||||
val itemNameSize = remember { mutableStateOf(0.dp) }
|
||||
|
||||
val filledIconButtonColors = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
|
|
@ -261,9 +276,10 @@ private fun LocationContent(
|
|||
.align(alignment = Alignment.Center)
|
||||
.offset { IntOffset(x = 0, y = scrollState.value / 2) },
|
||||
state = fantasyMapState,
|
||||
model = { item.value.map },
|
||||
previewPlaceholder = R.drawable.im_brulkhai,
|
||||
imageOptions = ImageOptions(contentScale = ContentScale.Fit),
|
||||
item = item,
|
||||
items = remember { derivedStateOf { item.value.marquees } },
|
||||
selectedItem = selectedIndex,
|
||||
highlight = mapHighlight,
|
||||
onMarquee = onMarquee,
|
||||
|
|
@ -323,18 +339,32 @@ private fun LocationContent(
|
|||
Handle(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterHorizontally)
|
||||
.padding(top = 16.dp)
|
||||
.padding(vertical = 16.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterHorizontally)
|
||||
.onSizeChanged { itemNameSize.value = with(density) { it.height.toDp() } }
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 16.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
text = item.value.name,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
text = item.value.name,
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.graphicsLayer { rotationZ = 180f },
|
||||
painter = painterResource(id = R.drawable.art_divider_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
modifier = Modifier
|
||||
|
|
@ -350,7 +380,7 @@ private fun LocationContent(
|
|||
MarqueeItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(this@constraint.maxHeight - 32.dp - itemNameSize.height),
|
||||
.height(this@constraint.maxHeight - 32.dp - itemNameSize.value),
|
||||
marquee = marquee,
|
||||
)
|
||||
}
|
||||
|
|
@ -378,6 +408,31 @@ private fun HandlePagerScroll(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberAnnotation(item: State<LocationDetailUio>): State<AnnotatedLocationDetailUio> {
|
||||
val typography = MaterialTheme.lexicon.typography
|
||||
|
||||
return remember(item) {
|
||||
derivedStateOf {
|
||||
AnnotatedLocationDetailUio(
|
||||
name = AnnotatedString(
|
||||
text = item.value.name,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.titleDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, item.value.name.length),
|
||||
)
|
||||
)
|
||||
),
|
||||
map = item.value.map,
|
||||
marquees = item.value.marquees.map { it.annotate(typography) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
private fun rememberSnapConnection(
|
||||
|
|
@ -437,7 +492,7 @@ private fun LocationPreview() {
|
|||
scrollState = rememberScrollState(),
|
||||
pagerState = rememberPagerState(),
|
||||
fantasyMapState = rememberFantasyMapState(),
|
||||
item = remember {
|
||||
item = rememberAnnotation(item = remember {
|
||||
mutableStateOf(
|
||||
LocationDetailUio(
|
||||
name = "Daggerfall",
|
||||
|
|
@ -456,7 +511,7 @@ private fun LocationPreview() {
|
|||
),
|
||||
)
|
||||
)
|
||||
},
|
||||
}),
|
||||
selectedIndex = remember { mutableStateOf(0) },
|
||||
mapHighlight = remember { mutableStateOf(Offset(0.5f, 0.5f)) },
|
||||
onBack = { },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.location.detail
|
|||
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.rplexicon.repository.LocationRepository
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ 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.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
|
|
@ -14,11 +13,16 @@ 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.AnnotatedString
|
||||
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
|
||||
import com.pixelized.rplexicon.ui.theme.typography.LexiconTypography
|
||||
import com.pixelized.rplexicon.utilitary.LOS_HOLLOW
|
||||
import com.pixelized.rplexicon.utilitary.extentions.annotateWithDropCap
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class MarqueeUio(
|
||||
|
|
@ -27,23 +31,49 @@ data class MarqueeUio(
|
|||
val description: String?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class AnnotatedMarqueeUio(
|
||||
val name: AnnotatedString,
|
||||
val position: Offset,
|
||||
val description: AnnotatedString?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
fun MarqueeUio.annotate(
|
||||
typography: LexiconTypography
|
||||
): AnnotatedMarqueeUio {
|
||||
return AnnotatedMarqueeUio(
|
||||
name = name.annotateWithDropCap(style = typography.bodyDropCapSpan),
|
||||
position = position,
|
||||
description = description?.annotateWithDropCap(style = typography.bodyDropCapSpan),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarqueeItem(
|
||||
modifier: Modifier = Modifier,
|
||||
marquee: MarqueeUio,
|
||||
marquee: AnnotatedMarqueeUio,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
text = marquee.name,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = LOS_HOLLOW,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 3,
|
||||
text = marquee.name,
|
||||
)
|
||||
}
|
||||
marquee.description?.let {
|
||||
Text(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
|
|
@ -65,6 +95,8 @@ private fun MarqueeItemPreview() {
|
|||
name = "Name",
|
||||
position = Offset.Zero,
|
||||
description = "description",
|
||||
).annotate(
|
||||
typography = MaterialTheme.lexicon.typography,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ fun LocationItem(
|
|||
true -> emptyList()
|
||||
else -> listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.dropCapMediumSpan,
|
||||
item = typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = 1,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -129,7 +128,7 @@ private fun QuestDetailUio.QuestStep.annotate(): AnnotatedQuestDetailUio.Annotat
|
|||
text = description,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.dropCapLargeSpan,
|
||||
item = typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = min(1, description.length),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ fun QuestItem(
|
|||
true -> emptyList()
|
||||
else -> listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.dropCapMediumSpan,
|
||||
item = typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = 1,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ fun lexiconDimen(
|
|||
thickness = 4.dp,
|
||||
),
|
||||
map: LexiconDimens.Map = LexiconDimens.Map(
|
||||
mapSnapPx = with(density) { 64.dp.roundToPx() },
|
||||
mapSnapPx = with(density) { 128.dp.roundToPx() },
|
||||
marqueeRadiusPx = with(density) { 12.dp.roundToPx() },
|
||||
marqueeStrokePx = with(density) { 2.dp.roundToPx() },
|
||||
crossRadiusPx = with(density) { 12.dp.roundToPx() / sqrt(2f) }.toInt(),
|
||||
|
|
|
|||
|
|
@ -24,22 +24,19 @@ val stampFontFamily = FontFamily(
|
|||
@Stable
|
||||
class LexiconTypography(
|
||||
val base: Typography = Typography(),
|
||||
val dropCapMedium: TextStyle = base.displaySmall.copy(
|
||||
val stamp: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = stampFontFamily,
|
||||
),
|
||||
val bodyDropCapSpan: SpanStyle = base.displaySmall.copy(
|
||||
fontFamily = regalFontFamily,
|
||||
baselineShift = BaselineShift(-0.3f),
|
||||
letterSpacing = (-6).sp
|
||||
),
|
||||
val dropCapLarge: TextStyle = base.displayMedium.copy(
|
||||
).toSpanStyle(),
|
||||
val titleDropCapSpan: SpanStyle = base.displayLarge.copy(
|
||||
fontFamily = regalFontFamily,
|
||||
baselineShift = BaselineShift.Subscript,
|
||||
baselineShift = BaselineShift(-0.3f),
|
||||
letterSpacing = (-8).sp
|
||||
),
|
||||
val stamp: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = stampFontFamily,
|
||||
)
|
||||
) {
|
||||
val dropCapMediumSpan: SpanStyle = dropCapMedium.toSpanStyle()
|
||||
val dropCapLargeSpan: SpanStyle = dropCapLarge.toSpanStyle()
|
||||
}
|
||||
).toSpanStyle()
|
||||
)
|
||||
|
||||
fun lexiconTypography() = LexiconTypography()
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.pixelized.rplexicon.utilitary.extentions
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.core.net.toUri
|
||||
|
||||
val String.ARG: String get() = "$this={$this}"
|
||||
|
|
@ -24,3 +28,18 @@ fun String?.toUriOrNull(): Uri? = try {
|
|||
null
|
||||
}
|
||||
|
||||
private val dropCapRegex = Regex("(?:\n\n)(.)")
|
||||
|
||||
@Stable
|
||||
fun String.annotateWithDropCap(
|
||||
style: SpanStyle,
|
||||
) = AnnotatedString(
|
||||
text = this,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = style,
|
||||
start = 0,
|
||||
end = Integer.min(1, this.length),
|
||||
),
|
||||
) + dropCapRegex.annotatedSpan(input = this, spanStyle = style)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue