Add map feature

This commit is contained in:
Thomas Andres Gomez 2023-08-08 15:59:30 +02:00
parent 34d15c41e0
commit e74bd7c097
13 changed files with 640 additions and 16 deletions

View file

@ -22,8 +22,12 @@ class MarqueeParser @Inject constructor() {
item is List<*> -> { item is List<*> -> {
val map = item.getOrNull(structure.map) as? String val map = item.getOrNull(structure.map) as? String
val name = item.getOrNull(structure.name) as? String val name = item.getOrNull(structure.name) as? String
val x = (item.getOrNull(structure.x) as? String)?.toFloatOrNull() val x = (item.getOrNull(structure.x) as? String)
val y = (item.getOrNull(structure.y) as? String)?.toFloatOrNull() ?.replace(oldValue = ",", newValue = ".")
?.toFloatOrNull()
val y = (item.getOrNull(structure.y) as? String)
?.replace(oldValue = ",", newValue = ".")
?.toFloatOrNull()
val description = item.getOrNull(structure.description) as? String? val description = item.getOrNull(structure.description) as? String?
if (map != null && name != null && x != null && y != null) { if (map != null && name != null && x != null && y != null) {

View file

@ -30,7 +30,9 @@ import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.pixelized.rplexicon.LocalSnack import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.pages.LEXICON_ROUTE import com.pixelized.rplexicon.ui.navigation.pages.LEXICON_LIST_ROUTE
import com.pixelized.rplexicon.ui.navigation.pages.LOCATION_LIST_ROUTE
import com.pixelized.rplexicon.ui.navigation.pages.QUEST_LIST_ROUTE
import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon
import com.pixelized.rplexicon.ui.navigation.pages.composableLocations import com.pixelized.rplexicon.ui.navigation.pages.composableLocations
import com.pixelized.rplexicon.ui.navigation.pages.composableQuests import com.pixelized.rplexicon.ui.navigation.pages.composableQuests
@ -50,7 +52,7 @@ fun HomeNavHost(
lexiconListState: LazyListState, lexiconListState: LazyListState,
questListState: LazyListState, questListState: LazyListState,
locationListState: LazyListState, locationListState: LazyListState,
startDestination: String = LEXICON_ROUTE startDestination: String = LOCATION_LIST_ROUTE
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
LocalSnack provides remember { SnackbarHostState() }, LocalSnack provides remember { SnackbarHostState() },
@ -69,15 +71,17 @@ fun HomeNavHost(
) )
}, },
bottomBar = { bottomBar = {
val selectedIndex = rememberSaveable { mutableStateOf(0) } val selectedIndex = rememberSaveable {
mutableStateOf(bottomBarItems.indexOfFirst { it.route == startDestination })
}
BottomAppBar( BottomAppBar(
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
tonalElevation = 0.dp, tonalElevation = 0.dp,
) { ) {
bottomBarItems.forEachIndexed { index, item -> bottomBarItems.forEachIndexed { index, item ->
val selectedItem = val selectedItem = remember {
remember { derivedStateOf { selectedIndex.value == index } } derivedStateOf { selectedIndex.value == index }
}
NavigationBarItem( NavigationBarItem(
selected = selectedItem.value, selected = selectedItem.value,
onClick = { onClick = {
@ -118,6 +122,7 @@ fun HomeNavHost(
@Stable @Stable
class BottomBarItem( class BottomBarItem(
val route: String,
val icon: Int, val icon: Int,
val label: Int, val label: Int,
val onClick: () -> Unit, val onClick: () -> Unit,
@ -132,16 +137,19 @@ private fun rememberBottomBarItems(
val option = navHostController.pageOption() val option = navHostController.pageOption()
listOf( listOf(
BottomBarItem( BottomBarItem(
route = LEXICON_LIST_ROUTE,
icon = R.drawable.ic_outline_account_circle_24, icon = R.drawable.ic_outline_account_circle_24,
label = R.string.home_lexicon, label = R.string.home_lexicon,
onClick = { navHostController.navigateToLexicon(option) } onClick = { navHostController.navigateToLexicon(option) }
), ),
BottomBarItem( BottomBarItem(
route = QUEST_LIST_ROUTE,
icon = R.drawable.ic_outline_scroll_24, icon = R.drawable.ic_outline_scroll_24,
label = R.string.home_quest_log, label = R.string.home_quest_log,
onClick = { navHostController.navigateToQuestList(option) } onClick = { navHostController.navigateToQuestList(option) }
), ),
BottomBarItem( BottomBarItem(
route = LOCATION_LIST_ROUTE,
icon = R.drawable.ic_outline_map_24, icon = R.drawable.ic_outline_map_24,
label = R.string.home_location, label = R.string.home_location,
onClick = { navHostController.navigateToLocation(option) } onClick = { navHostController.navigateToLocation(option) }

View file

@ -1,7 +1,6 @@
package com.pixelized.rplexicon.ui.navigation package com.pixelized.rplexicon.ui.navigation
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
@ -16,6 +15,7 @@ import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
import com.pixelized.rplexicon.ui.navigation.screens.composableHome import com.pixelized.rplexicon.ui.navigation.screens.composableHome
import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconDetail import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconDetail
import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconSearch import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconSearch
import com.pixelized.rplexicon.ui.navigation.screens.composableLocationDetail
import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail
val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> { val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> {
@ -45,11 +45,10 @@ fun ScreenNavHost(
questListState = questListState, questListState = questListState,
locationListState = locationListState locationListState = locationListState
) )
composableLexiconDetail() composableLexiconDetail()
composableLexiconSearch() composableLexiconSearch()
composableQuestDetail() composableQuestDetail()
composableLocationDetail()
} }
} }
} }

View file

@ -10,13 +10,13 @@ import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconScreen
private const val ROUTE = "lexicon" private const val ROUTE = "lexicon"
const val LEXICON_ROUTE = ROUTE const val LEXICON_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableLexicon( fun NavGraphBuilder.composableLexicon(
lazyListState: LazyListState, lazyListState: LazyListState,
) { ) {
animatedComposable( animatedComposable(
route = LEXICON_ROUTE, route = LEXICON_LIST_ROUTE,
animation = NavigationAnimation.NONE, animation = NavigationAnimation.NONE,
) { ) {
LexiconScreen( LexiconScreen(

View file

@ -10,13 +10,13 @@ import com.pixelized.rplexicon.ui.screens.location.list.LocationScreen
private const val ROUTE = "locations" private const val ROUTE = "locations"
const val LOCATION_ROUTE = ROUTE const val LOCATION_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableLocations( fun NavGraphBuilder.composableLocations(
lazyListState: LazyListState, lazyListState: LazyListState,
) { ) {
animatedComposable( animatedComposable(
route = LOCATION_ROUTE, route = LOCATION_LIST_ROUTE,
animation = NavigationAnimation.NONE, animation = NavigationAnimation.NONE,
) { ) {
LocationScreen( LocationScreen(

View file

@ -0,0 +1,55 @@
package com.pixelized.rplexicon.ui.navigation.screens
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
import com.pixelized.rplexicon.ui.navigation.animatedComposable
import com.pixelized.rplexicon.ui.screens.location.detail.LocationDetail
import com.pixelized.rplexicon.utilitary.extentions.ARG
private const val ROUTE = "LocationDetail"
private const val ARG_ID = "id"
val LOCATION_DETAIL_ROUTE = ROUTE +
"?${ARG_ID.ARG}"
@Stable
@Immutable
data class LocationDetailArgument(
val id: Int,
)
val SavedStateHandle.locationDetailArgument: LocationDetailArgument
get() = LocationDetailArgument(
id = get(ARG_ID) ?: error("CharacterDetailArgument argument: $ARG_ID"),
)
fun NavGraphBuilder.composableLocationDetail() {
animatedComposable(
route = LOCATION_DETAIL_ROUTE,
arguments = listOf(
navArgument(name = ARG_ID) {
type = NavType.IntType
},
),
animation = NavigationAnimation.Push,
) {
LocationDetail()
}
}
fun NavHostController.navigateToLocationDetail(
id: Int,
option: NavOptionsBuilder.() -> Unit = {},
) {
val route = ROUTE +
"?$ARG_ID=$id"
navigate(route = route, builder = option)
}

View file

@ -0,0 +1,183 @@
package com.pixelized.rplexicon.ui.screens.location.detail
import androidx.annotation.DrawableRes
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateOffsetAsState
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.skydoves.landscapist.ImageOptions
@Composable
fun FantasyMap(
modifier: Modifier = Modifier,
state: FantasyMapState,
imageOptions: ImageOptions = ImageOptions(),
@DrawableRes previewPlaceholder: Int,
item: State<LocationDetailUio>,
selectedItem: State<Int>,
onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
) {
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
val animatedOffset = animateOffsetAsState(targetValue = state.offset, label = "OffsetAnimation")
LaunchedEffect(key1 = "CenterOnMarquee:${selectedItem.value}") {
item.value.marquees.getOrNull(selectedItem.value)?.position
?.let { state.pan(state.computeMarqueeOffset(it)) }
}
Box(
modifier = modifier
.pointerInput("DetectTransformGestures") {
detectTransformGestures(panZoomLock = true) { _, pan, zoom, _ ->
state.scale(
scale = state.scale * zoom,
)
state.pan(
offset = Offset(
x = state.offset.x + pan.x,
y = state.offset.y + pan.y,
),
)
}
}
.graphicsLayer {
scaleX = animatedScale.value
scaleY = animatedScale.value
translationX = animatedOffset.value.x
translationY = animatedOffset.value.y
},
contentAlignment = Alignment.Center,
) {
AsyncImage(
modifier = Modifier
.onSizeChanged { state.imageSize = it }
.drawWithContent {
drawContent()
item.value.marquees.forEachIndexed { index, item ->
drawCircle(
color = when (selectedItem.value) {
index -> Color.Cyan
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
)
)
}
}
.pointerInput("DetectTapGestures") {
detectTapGestures(
onTap = { tap ->
val marquee = item.value.marquees
.asReversed()
.firstOrNull { item ->
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) }
}
marquee?.let(onMarquee)
}
)
},
imageModel = { item.value.map },
imageOptions = imageOptions,
previewPlaceholder = previewPlaceholder,
)
}
}
@Composable
@Stable
fun rememberFantasyMapState(
initialScale: Float = 1f,
initialOffset: Offset = Offset.Zero,
minScale: Float = 1f,
maxScale: Float = 5f,
): FantasyMapState {
return remember {
FantasyMapState(
initialScale = initialScale,
initialOffset = initialOffset,
minScale = minScale,
maxScale = maxScale,
)
}
}
@Stable
class FantasyMapState(
initialScale: Float = 1f,
initialOffset: Offset = Offset.Zero,
val minScale: Float = 1f,
val maxScale: Float = 5f,
) {
private val maxX by derivedStateOf { imageSize.width * scale / 2f }
private val minX by derivedStateOf { -maxX }
private val maxY by derivedStateOf { imageSize.height * scale / 2f }
private val minY by derivedStateOf { -maxY }
private val _scale: MutableState<Float> = mutableStateOf(initialScale)
val scale: Float by _scale
private val _offset: MutableState<Offset> = mutableStateOf(initialOffset)
val offset: Offset by _offset
private val _imageSize: MutableState<IntSize> = mutableStateOf(IntSize.Zero)
var imageSize: IntSize by _imageSize
@Stable
fun scale(
scale: Float,
) {
_scale.value = maxOf(minScale, minOf(scale, maxScale))
}
@Stable
fun pan(
offset: Offset,
) {
_offset.value = Offset(
x = maxOf(minX, minOf(maxX, offset.x)),
y = maxOf(minY, minOf(maxY, offset.y)),
)
}
@Stable
fun computeMarqueeOffset(
origin: Offset,
size: IntSize = imageSize,
): Offset {
return Offset(
x = (size.width / 2f - origin.x * size.width) * scale,
y = (size.height / 2f - origin.y * size.height) * scale,
)
}
}

View file

@ -0,0 +1,321 @@
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.layout.Box
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.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.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.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.skydoves.landscapist.ImageOptions
import kotlinx.coroutines.launch
import kotlin.math.max
@Stable
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
fun LocationDetail(
viewModel: LocationDetailViewModel = hiltViewModel()
) {
val scope = rememberCoroutineScope()
val pager = rememberPagerState()
val fantasy = rememberFantasyMapState()
val screen = LocalScreenNavHost.current
val selectedIndex = remember { mutableStateOf(0) }
Surface {
LocationContent(
modifier = Modifier.fillMaxSize(),
pagerState = pager,
fantasyMapState = fantasy,
item = viewModel.location,
selectedIndex = selectedIndex,
onBack = {
screen.popBackStack()
},
onMarquee = {
scope.launch {
val index = max(viewModel.location.value.marquees.indexOf(it), 0)
selectedIndex.value = index
pager.animateScrollToPage(page = index)
}
},
onCenter = {
fantasy.scale(scale = 1f)
fantasy.pan(offset = Offset.Zero)
},
onZoomIn = {
fantasy.scale(fantasy.scale + 1)
fantasy.pan(offset = fantasy.offset)
},
onZoomOut = {
fantasy.scale(fantasy.scale - 1)
fantasy.pan(offset = fantasy.offset)
},
)
HandlePagerScroll(
pagerState = pager,
index = selectedIndex,
onIndexChange = { page ->
pager.animateScrollToPage(page = page)
},
onPageChange = { page ->
selectedIndex.value = page
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
private fun LocationContent(
modifier: Modifier,
pagerState: PagerState,
fantasyMapState: FantasyMapState,
item: State<LocationDetailUio>,
selectedIndex: State<Int>,
onBack: () -> Unit,
onMarquee: (LocationDetailUio.MarqueeUio) -> Unit,
onCenter: () -> Unit,
onZoomIn: () -> Unit,
onZoomOut: () -> Unit,
) {
Scaffold(
modifier = modifier,
containerColor = Color.Transparent,
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24),
contentDescription = null
)
}
},
title = {
Text(text = stringResource(id = R.string.detail_title))
},
)
},
) { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues = paddingValues),
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.weight(weight = 2f),
tonalElevation = 2.dp,
) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(shape = RectangleShape),
) {
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),
) {
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,
)
}
}
}
}
}
@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(
pagerState: PagerState,
index: State<Int>,
onIndexChange: suspend (page: Int) -> Unit,
onPageChange: suspend (page: Int) -> Unit,
) {
if (!pagerState.isScrollInProgress) {
LaunchedEffect(Unit) {
onPageChange.invoke(pagerState.currentPage)
}
}
LaunchedEffect(key1 = index.value) {
onIndexChange.invoke(index.value)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun LocationPreview() {
LexiconTheme {
Surface {
LocationContent(
modifier = Modifier.fillMaxSize(),
pagerState = rememberPagerState(),
fantasyMapState = rememberFantasyMapState(),
item = remember {
mutableStateOf(
LocationDetailUio(
name = "Daggerfall",
map = Uri.parse("https://i.pinimg.com/originals/6d/56/cd/6d56cd9358cc94a7077157ea3c1b5842.jpg"),
marquees = listOf(
LocationDetailUio.MarqueeUio(
name = "start",
position = Offset.Zero,
description = "Marquee en haut à gauche."
),
LocationDetailUio.MarqueeUio(
name = "end",
position = Offset(1f, 1f),
description = "Marquee en bas à droite."
),
),
)
)
},
selectedIndex = remember { mutableStateOf(0) },
onBack = { },
onMarquee = { },
onCenter = { },
onZoomIn = { },
onZoomOut = { },
)
}
}
}

View file

@ -0,0 +1,38 @@
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
import com.pixelized.rplexicon.ui.navigation.screens.locationDetailArgument
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class LocationDetailViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
repository: LocationRepository,
) : ViewModel() {
val location: State<LocationDetailUio>
init {
val argument = savedStateHandle.locationDetailArgument
val source = repository.data.value[argument.id]
location = mutableStateOf(
LocationDetailUio(
name = source.name,
map = source.uri,
marquees = source.marquees.map { marquee ->
LocationDetailUio.MarqueeUio(
name = marquee.name,
position = Offset(x = marquee.x, y = marquee.y),
description = marquee.description,
)
}
)
)
}
}

View file

@ -28,6 +28,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.ui.composable.Loader import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocationDetail
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.cell import com.pixelized.rplexicon.utilitary.extentions.cell
import com.pixelized.rplexicon.utilitary.extentions.lexicon import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -59,7 +60,7 @@ fun LocationScreen(
refreshState = refresh, refreshState = refresh,
refreshing = viewModel.isLoading, refreshing = viewModel.isLoading,
onItem = { onItem = {
// screen.navigateToQuestDetail(id = it.id) screen.navigateToLocationDetail(id = it.id)
}, },
) )

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,9l0,-6L7,3l0,2.59L3.91,2.5L2.5,3.91L5.59,7L3,7l0,2L9,9zM21,9V7l-2.59,0l3.09,-3.09L20.09,2.5L17,5.59V3l-2,0l0,6L21,9zM3,15l0,2h2.59L2.5,20.09l1.41,1.41L7,18.41L7,21h2l0,-6L3,15zM15,15l0,6h2v-2.59l3.09,3.09l1.41,-1.41L18.41,17H21v-2L15,15z"/>
</vector>