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,
 | 
			
		||||
    item: State<LocationDetailUio>,
 | 
			
		||||
    selectedItem: State<Int>,
 | 
			
		||||
    onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
 | 
			
		||||
    onMarquee: (MarqueeUio) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
 | 
			
		||||
    val animatedOffset = animateOffsetAsState(targetValue = state.offset, label = "OffsetAnimation")
 | 
			
		||||
| 
						 | 
				
			
			@ -52,14 +52,19 @@ fun FantasyMap(
 | 
			
		|||
        modifier = modifier
 | 
			
		||||
            .pointerInput("DetectTransformGestures") {
 | 
			
		||||
                detectTransformGestures(panZoomLock = true) { _, pan, zoom, _ ->
 | 
			
		||||
                    val newScale = state.scale * zoom
 | 
			
		||||
                    val oldScale = state.scale
 | 
			
		||||
                    val oldOffset = state.offset
 | 
			
		||||
 | 
			
		||||
                    state.scale(
 | 
			
		||||
                        scale = state.scale * zoom,
 | 
			
		||||
                        scale = newScale
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    state.pan(
 | 
			
		||||
                        offset = Offset(
 | 
			
		||||
                            x = state.offset.x + pan.x,
 | 
			
		||||
                            y = state.offset.y + pan.y,
 | 
			
		||||
                        ),
 | 
			
		||||
                            x = oldOffset.x * newScale / oldScale + pan.x,
 | 
			
		||||
                            y = oldOffset.y * newScale / oldScale + pan.y,
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +104,7 @@ fun FantasyMap(
 | 
			
		|||
                            val marquee = item.value.marquees
 | 
			
		||||
                                .asReversed()
 | 
			
		||||
                                .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.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.net.Uri
 | 
			
		||||
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.BoxWithConstraints
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
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.pager.HorizontalPager
 | 
			
		||||
import androidx.compose.foundation.pager.PagerState
 | 
			
		||||
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.FilledIconButton
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
| 
						 | 
				
			
			@ -34,21 +41,24 @@ 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.Color
 | 
			
		||||
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.res.painterResource
 | 
			
		||||
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.unit.IntOffset
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
			
		||||
import com.pixelized.rplexicon.R
 | 
			
		||||
import com.pixelized.rplexicon.ui.composable.Handle
 | 
			
		||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
			
		||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
			
		||||
import com.skydoves.landscapist.ImageOptions
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -56,14 +66,7 @@ data class LocationDetailUio(
 | 
			
		|||
    val name: String,
 | 
			
		||||
    val map: Uri,
 | 
			
		||||
    val marquees: List<MarqueeUio>,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class MarqueeUio(
 | 
			
		||||
        val name: String,
 | 
			
		||||
        val position: Offset,
 | 
			
		||||
        val description: String?,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalFoundationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -71,15 +74,35 @@ fun LocationDetail(
 | 
			
		|||
    viewModel: LocationDetailViewModel = hiltViewModel()
 | 
			
		||||
) {
 | 
			
		||||
    val scope = rememberCoroutineScope()
 | 
			
		||||
    val scroll = rememberScrollState()
 | 
			
		||||
    val pager = rememberPagerState()
 | 
			
		||||
    val fantasy = rememberFantasyMapState()
 | 
			
		||||
    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) }
 | 
			
		||||
 | 
			
		||||
    Surface {
 | 
			
		||||
        LocationContent(
 | 
			
		||||
            modifier = Modifier.fillMaxSize(),
 | 
			
		||||
            connection = connection,
 | 
			
		||||
            scrollState = scroll,
 | 
			
		||||
            pagerState = pager,
 | 
			
		||||
            fantasyMapState = fantasy,
 | 
			
		||||
            item = viewModel.location,
 | 
			
		||||
| 
						 | 
				
			
			@ -99,12 +122,32 @@ fun LocationDetail(
 | 
			
		|||
                fantasy.pan(offset = Offset.Zero)
 | 
			
		||||
            },
 | 
			
		||||
            onZoomIn = {
 | 
			
		||||
                fantasy.scale(fantasy.scale + 1)
 | 
			
		||||
                fantasy.pan(offset = fantasy.offset)
 | 
			
		||||
                val newScale = fantasy.scale + 1
 | 
			
		||||
                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 = {
 | 
			
		||||
                fantasy.scale(fantasy.scale - 1)
 | 
			
		||||
                fantasy.pan(offset = fantasy.offset)
 | 
			
		||||
                val newScale = fantasy.scale - 1
 | 
			
		||||
                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
 | 
			
		||||
private fun LocationContent(
 | 
			
		||||
    modifier: Modifier,
 | 
			
		||||
    connection: NestedScrollConnection,
 | 
			
		||||
    scrollState: ScrollState,
 | 
			
		||||
    pagerState: PagerState,
 | 
			
		||||
    fantasyMapState: FantasyMapState,
 | 
			
		||||
    item: State<LocationDetailUio>,
 | 
			
		||||
    selectedIndex: State<Int>,
 | 
			
		||||
    onBack: () -> Unit,
 | 
			
		||||
    onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
 | 
			
		||||
    onMarquee: (MarqueeUio) -> Unit,
 | 
			
		||||
    onCenter: () -> Unit,
 | 
			
		||||
    onZoomIn: () -> Unit,
 | 
			
		||||
    onZoomOut: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    Scaffold(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        containerColor = Color.Transparent,
 | 
			
		||||
        topBar = {
 | 
			
		||||
            TopAppBar(
 | 
			
		||||
                navigationIcon = {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,117 +193,110 @@ private fun LocationContent(
 | 
			
		|||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                title = {
 | 
			
		||||
                    Text(text = stringResource(id = R.string.detail_title))
 | 
			
		||||
                    Text(text = stringResource(id = R.string.map_title))
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        },
 | 
			
		||||
    ) { paddingValues ->
 | 
			
		||||
        Column(
 | 
			
		||||
        BoxWithConstraints(
 | 
			
		||||
            modifier = Modifier.padding(paddingValues = paddingValues),
 | 
			
		||||
        ) {
 | 
			
		||||
            Surface(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .fillMaxWidth()
 | 
			
		||||
                    .weight(weight = 2f),
 | 
			
		||||
                tonalElevation = 2.dp,
 | 
			
		||||
        ) constraint@{
 | 
			
		||||
            Column(
 | 
			
		||||
                modifier = Modifier.verticalScroll(state = scrollState),
 | 
			
		||||
            ) {
 | 
			
		||||
                Box(
 | 
			
		||||
                Surface(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxSize()
 | 
			
		||||
                        .clip(shape = RectangleShape),
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .heightIn(
 | 
			
		||||
                            min = this@constraint.maxHeight / 2,
 | 
			
		||||
                            max = this@constraint.maxHeight * 2 / 3,
 | 
			
		||||
                        ),
 | 
			
		||||
                    tonalElevation = 2.dp,
 | 
			
		||||
                ) {
 | 
			
		||||
                    FantasyMap(
 | 
			
		||||
                        modifier = Modifier.align(alignment = Alignment.Center),
 | 
			
		||||
                        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),
 | 
			
		||||
                    Box(
 | 
			
		||||
                        modifier = Modifier.clip(shape = RectangleShape),
 | 
			
		||||
                    ) {
 | 
			
		||||
                        val colors = IconButtonDefaults.filledIconButtonColors(
 | 
			
		||||
                            containerColor = MaterialTheme.colorScheme.surface,
 | 
			
		||||
                            contentColor = MaterialTheme.colorScheme.onSurface,
 | 
			
		||||
                        FantasyMap(
 | 
			
		||||
                            modifier = Modifier
 | 
			
		||||
                                .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(
 | 
			
		||||
                            onClick = onZoomOut,
 | 
			
		||||
                            colors = colors,
 | 
			
		||||
                        Column(
 | 
			
		||||
                            modifier = Modifier
 | 
			
		||||
                                .align(alignment = Alignment.BottomEnd)
 | 
			
		||||
                                .padding(all = 16.dp),
 | 
			
		||||
                        ) {
 | 
			
		||||
                            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
 | 
			
		||||
                            val colors = IconButtonDefaults.filledIconButtonColors(
 | 
			
		||||
                                containerColor = MaterialTheme.colorScheme.surface,
 | 
			
		||||
                                contentColor = MaterialTheme.colorScheme.onSurface,
 | 
			
		||||
                            )
 | 
			
		||||
                            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(
 | 
			
		||||
                modifier = Modifier.weight(weight = 1f),
 | 
			
		||||
                state = pagerState,
 | 
			
		||||
                pageCount = item.value.marquees.size,
 | 
			
		||||
                contentPadding = PaddingValues(all = 16.dp),
 | 
			
		||||
                pageSpacing = 8.dp,
 | 
			
		||||
            ) {
 | 
			
		||||
                item.value.marquees.getOrNull(it)?.let { marquee ->
 | 
			
		||||
                    Marquee(
 | 
			
		||||
                        modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                        marquee = marquee,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                Handle(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .align(alignment = Alignment.CenterHorizontally)
 | 
			
		||||
                        .padding(top = 16.dp)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                HorizontalPager(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .nestedScroll(connection),
 | 
			
		||||
                    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)
 | 
			
		||||
@Composable
 | 
			
		||||
private fun HandlePagerScroll(
 | 
			
		||||
| 
						 | 
				
			
			@ -287,6 +324,8 @@ private fun LocationPreview() {
 | 
			
		|||
        Surface {
 | 
			
		||||
            LocationContent(
 | 
			
		||||
                modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                connection = remember { object : NestedScrollConnection {} },
 | 
			
		||||
                scrollState = rememberScrollState(),
 | 
			
		||||
                pagerState = rememberPagerState(),
 | 
			
		||||
                fantasyMapState = rememberFantasyMapState(),
 | 
			
		||||
                item = remember {
 | 
			
		||||
| 
						 | 
				
			
			@ -295,12 +334,12 @@ private fun LocationPreview() {
 | 
			
		|||
                            name = "Daggerfall",
 | 
			
		||||
                            map = Uri.parse("https://i.pinimg.com/originals/6d/56/cd/6d56cd9358cc94a7077157ea3c1b5842.jpg"),
 | 
			
		||||
                            marquees = listOf(
 | 
			
		||||
                                LocationDetailUio.MarqueeUio(
 | 
			
		||||
                                MarqueeUio(
 | 
			
		||||
                                    name = "start",
 | 
			
		||||
                                    position = Offset.Zero,
 | 
			
		||||
                                    description = "Marquee en haut à gauche."
 | 
			
		||||
                                ),
 | 
			
		||||
                                LocationDetailUio.MarqueeUio(
 | 
			
		||||
                                MarqueeUio(
 | 
			
		||||
                                    name = "end",
 | 
			
		||||
                                    position = Offset(1f, 1f),
 | 
			
		||||
                                    description = "Marquee en bas à droite."
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ class LocationDetailViewModel @Inject constructor(
 | 
			
		|||
                name = source.name,
 | 
			
		||||
                map = source.uri,
 | 
			
		||||
                marquees = source.marquees.map { marquee ->
 | 
			
		||||
                    LocationDetailUio.MarqueeUio(
 | 
			
		||||
                    MarqueeUio(
 | 
			
		||||
                        name = marquee.name,
 | 
			
		||||
                        position = Offset(x = marquee.x, y = marquee.y),
 | 
			
		||||
                        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 navigation: Color,
 | 
			
		||||
    val placeholder: Color,
 | 
			
		||||
    val handle: Color,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +33,7 @@ fun darkColorScheme(
 | 
			
		|||
    status = status,
 | 
			
		||||
    navigation = navigation,
 | 
			
		||||
    placeholder = placeholder,
 | 
			
		||||
    handle = base.onSurface.copy(alpha = 0.5f),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -50,4 +52,5 @@ fun lightColorScheme(
 | 
			
		|||
    status = status,
 | 
			
		||||
    navigation = navigation,
 | 
			
		||||
    placeholder = placeholder,
 | 
			
		||||
    handle = base.onSurface.copy(alpha = 0.5f),
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,15 @@ data class LexiconDimens(
 | 
			
		|||
    val item: Dp,
 | 
			
		||||
    val detailPadding: Dp,
 | 
			
		||||
    val itemListPadding: PaddingValues,
 | 
			
		||||
)
 | 
			
		||||
    val handle: Handle
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    @Immutable
 | 
			
		||||
    data class Handle(
 | 
			
		||||
        val width: Dp,
 | 
			
		||||
        val thickness: Dp,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun lexiconDimen(
 | 
			
		||||
    itemHeight: Dp = 52.dp,
 | 
			
		||||
| 
						 | 
				
			
			@ -21,8 +29,13 @@ fun lexiconDimen(
 | 
			
		|||
        top = 8.dp,
 | 
			
		||||
        bottom = 8.dp + 16.dp + 56.dp + 16.dp,
 | 
			
		||||
    ),
 | 
			
		||||
    handle: LexiconDimens.Handle = LexiconDimens.Handle(
 | 
			
		||||
        width = 32.dp,
 | 
			
		||||
        thickness = 4.dp,
 | 
			
		||||
    ),
 | 
			
		||||
) = LexiconDimens(
 | 
			
		||||
    item = itemHeight,
 | 
			
		||||
    detailPadding = detailPadding,
 | 
			
		||||
    itemListPadding = itemListPadding
 | 
			
		||||
    itemListPadding = itemListPadding,
 | 
			
		||||
    handle = handle,
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -52,4 +52,6 @@
 | 
			
		|||
    <string name="quest_detail_area">Lieu :</string>
 | 
			
		||||
    <string name="quest_detail_individual_reward">Récompense individuelle :</string>
 | 
			
		||||
    <string name="quest_detail_group_rewars">Récompense de groupe :</string>
 | 
			
		||||
 | 
			
		||||
    <string name="map_title">Carte</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -53,4 +53,5 @@
 | 
			
		|||
    <string name="quest_detail_individual_reward">Individual reward:</string>
 | 
			
		||||
    <string name="quest_detail_group_rewars">Group reward:</string>
 | 
			
		||||
 | 
			
		||||
    <string name="map_title">Map</string>
 | 
			
		||||
</resources>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue