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