Category management - Maps
This commit is contained in:
parent
fa7fcbeae6
commit
012e8844cb
7 changed files with 158 additions and 36 deletions
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.geometry.Offset
|
|||
data class Location(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val uri: Uri?,
|
||||
val description: String?,
|
||||
val child: List<Pair<Offset, Location>> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class LocationParser @Inject constructor(
|
|||
Location(
|
||||
id = localMap.name,
|
||||
name = localMap.name,
|
||||
category = localMap.category,
|
||||
uri = localMap.uri,
|
||||
description = localMap.description,
|
||||
child = emptyList(),
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ class MapParser @Inject constructor() {
|
|||
|
||||
sheet.forEachDataLine(columns = COLUMNS) {
|
||||
val name = it.parse(column = NAME)
|
||||
val uri = it.parseUri(column = URI)
|
||||
val description = it.parse(column = DESCRIPTION)
|
||||
if (name != null) {
|
||||
val map = MapDto(
|
||||
name = name,
|
||||
uri = uri,
|
||||
description = description,
|
||||
category = it.parse(column = CATEGORY),
|
||||
uri = it.parseUri(column = URI),
|
||||
description = it.parse(column = DESCRIPTION),
|
||||
)
|
||||
maps.add(map)
|
||||
}
|
||||
|
|
@ -32,14 +31,16 @@ class MapParser @Inject constructor() {
|
|||
|
||||
data class MapDto(
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val uri: Uri?,
|
||||
val description: String?,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val NAME = column("Nom")
|
||||
private val CATEGORY = column("Catégorie")
|
||||
private val URI = column("Carte")
|
||||
private val DESCRIPTION = column("Description")
|
||||
private val COLUMNS get() = listOf(NAME, URI, DESCRIPTION)
|
||||
private val COLUMNS get() = listOf(NAME, CATEGORY, URI, DESCRIPTION)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.rplexicon.ui.screens.location.list
|
||||
|
||||
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.text.font.FontWeight
|
||||
|
||||
@Stable
|
||||
data class LocationCategoryUio(
|
||||
val title: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LocationCategory(
|
||||
modifier: Modifier = Modifier,
|
||||
item: LocationCategoryUio,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = item.title,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.pixelized.rplexicon.ui.screens.location.list
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
data class LocationGroupUio(
|
||||
val category: LocationCategoryUio?,
|
||||
val maps: List<LocationItemUio>,
|
||||
)
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedContent
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.ui.composable.Loader
|
||||
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
|
||||
|
|
@ -76,7 +78,7 @@ private fun LocationContent(
|
|||
lazyColumnState: LazyListState,
|
||||
refreshState: PullRefreshState,
|
||||
refreshing: State<Boolean>,
|
||||
items: State<List<LocationItemUio>>,
|
||||
items: State<List<LocationGroupUio>>,
|
||||
onItem: (LocationItemUio) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -111,17 +113,31 @@ private fun LocationContent(
|
|||
state = lazyColumnState,
|
||||
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
|
||||
) {
|
||||
items(
|
||||
items = items.value,
|
||||
key = { it.id },
|
||||
contentType = { "Location" },
|
||||
) {
|
||||
LocationItem(
|
||||
modifier = Modifier
|
||||
.clickable { onItem(it) }
|
||||
.cell(),
|
||||
item = it,
|
||||
)
|
||||
items.value.forEachIndexed { index, entry ->
|
||||
entry.category?.let {
|
||||
item(
|
||||
contentType = { "Header" },
|
||||
) {
|
||||
LocationCategory(
|
||||
modifier = Modifier
|
||||
.padding(top = if (index == 0) 0.dp else 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
item = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = entry.maps,
|
||||
key = { it.id },
|
||||
contentType = { "Location" },
|
||||
) {
|
||||
LocationItem(
|
||||
modifier = Modifier
|
||||
.clickable { onItem(it) }
|
||||
.cell(),
|
||||
item = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,9 +168,14 @@ private fun QuestListPreview() {
|
|||
items = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
LocationItemUio.preview(id = "0", title = "Daggerfall"),
|
||||
LocationItemUio.preview(id = "1", title = "Athkatla"),
|
||||
)
|
||||
LocationGroupUio(
|
||||
category = null,
|
||||
maps = listOf(
|
||||
LocationItemUio.preview(id = "0", title = "Daggerfall"),
|
||||
LocationItemUio.preview(id = "1", title = "Athkatla"),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
},
|
||||
onItem = { },
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
package com.pixelized.rplexicon.ui.screens.location.list
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.CategoryOrderRepository
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.LocationRepository
|
||||
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
|
||||
import com.pixelized.rplexicon.ui.screens.quest.list.QuestListViewModel
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.context
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LocationViewModel @Inject constructor(
|
||||
private val repository: LocationRepository,
|
||||
) : ViewModel() {
|
||||
private val orderRepository: CategoryOrderRepository,
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
val isLoading: State<Boolean> get() = _isLoading
|
||||
|
||||
private val _items = mutableStateOf<List<LocationItemUio>>(emptyList())
|
||||
val items: State<List<LocationItemUio>> get() = _items
|
||||
private val _items = mutableStateOf<List<LocationGroupUio>>(emptyList())
|
||||
val items: State<List<LocationGroupUio>> get() = _items
|
||||
|
||||
private val _error = MutableSharedFlow<FetchErrorUio>()
|
||||
val error: SharedFlow<FetchErrorUio> get() = _error
|
||||
|
|
@ -31,14 +44,36 @@ class LocationViewModel @Inject constructor(
|
|||
init {
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
repository.data.collect { items ->
|
||||
_items.value = items.map { item ->
|
||||
LocationItemUio(
|
||||
id = item.name,
|
||||
title = item.name,
|
||||
)
|
||||
orderRepository.data.combine(repository.data) { _, location -> location }
|
||||
.collect { items ->
|
||||
_items.value = items
|
||||
.sortedBy { it.name }
|
||||
.groupBy(
|
||||
keySelector = { entry ->
|
||||
LocationCategoryUio(
|
||||
title = entry.category
|
||||
?: context.getString(R.string.default_category_other)
|
||||
)
|
||||
},
|
||||
valueTransform = { entry ->
|
||||
LocationItemUio(
|
||||
id = entry.name,
|
||||
title = entry.name,
|
||||
)
|
||||
}
|
||||
)
|
||||
.map { entry ->
|
||||
LocationGroupUio(
|
||||
category = entry.key,
|
||||
maps = entry.value,
|
||||
)
|
||||
}
|
||||
.sortedBy { item ->
|
||||
item.category?.title?.let {
|
||||
orderRepository.findMapOrder(quest = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
update(force = false)
|
||||
|
|
@ -46,8 +81,22 @@ class LocationViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun update(force: Boolean) {
|
||||
_isLoading.value = true
|
||||
suspend fun update(force: Boolean) = coroutineScope {
|
||||
withContext(Dispatchers.Main) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
awaitAll(
|
||||
async { fetchLocation(force = force) },
|
||||
async { fetchCategoryOrder(force = force) },
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchLocation(force: Boolean) {
|
||||
try {
|
||||
if (force || repository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
repository.fetchMap()
|
||||
|
|
@ -63,9 +112,23 @@ class LocationViewModel @Inject constructor(
|
|||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Default)
|
||||
}
|
||||
// clean the laoding state
|
||||
finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
private suspend fun fetchCategoryOrder(force: Boolean) {
|
||||
try {
|
||||
if (force || orderRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
orderRepository.fetchCategoryOrder()
|
||||
}
|
||||
}
|
||||
// the data sheet structure is not as expected
|
||||
catch (exception: IncompatibleSheetStructure) {
|
||||
Log.e(QuestListViewModel.TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.CATEGORY_ORDER))
|
||||
}
|
||||
// default exception
|
||||
catch (exception: Exception) {
|
||||
Log.e(QuestListViewModel.TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Default)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue