add map list tab

This commit is contained in:
Thomas Andres Gomez 2023-08-07 12:33:56 +02:00
parent 87a1471efe
commit 57d1273435
17 changed files with 527 additions and 82 deletions

View file

@ -32,6 +32,7 @@ class LocationParser @Inject constructor() {
sheetIndex = index,
name = name,
uri = uri,
marquees = emptyList(),
)
} else {
null

View file

@ -0,0 +1,57 @@
package com.pixelized.rplexicon.ui.composable.error
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@Stable
sealed class FetchErrorUio {
@Stable
data class Permission(val intent: Intent) : FetchErrorUio()
@Stable
object Structure : FetchErrorUio()
@Stable
object Default : FetchErrorUio()
}
@Composable
fun HandleFetchError(
errors: SharedFlow<FetchErrorUio>,
onPermissionGranted: suspend () -> Unit,
) {
val context = LocalContext.current
val snack = LocalSnack.current
val scope = rememberCoroutineScope()
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
scope.launch {
onPermissionGranted()
}
}
}
LaunchedEffect(key1 = "LexiconErrorManagement") {
errors.collect { error ->
when (error) {
is FetchErrorUio.Permission -> launcher.launch(error.intent)
is FetchErrorUio.Structure -> snack.showSnackbar(message = context.getString(R.string.error_structure))
is FetchErrorUio.Default -> snack.showSnackbar(message = context.getString(R.string.error_generic))
}
}
}
}

View file

@ -1,7 +1,6 @@
package com.pixelized.rplexicon.ui.navigation
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.BottomAppBar
@ -33,8 +32,10 @@ import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.pages.LEXICON_ROUTE
import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon
import com.pixelized.rplexicon.ui.navigation.pages.composableQuestList
import com.pixelized.rplexicon.ui.navigation.pages.composableLocations
import com.pixelized.rplexicon.ui.navigation.pages.composableQuests
import com.pixelized.rplexicon.ui.navigation.pages.navigateToLexicon
import com.pixelized.rplexicon.ui.navigation.pages.navigateToLocation
import com.pixelized.rplexicon.ui.navigation.pages.navigateToQuestList
val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
@ -44,9 +45,11 @@ val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun HomeNavHost(
lexiconListState: LazyListState,
navHostController: NavHostController = rememberAnimatedNavController(),
bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController = navHostController),
bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController),
lexiconListState: LazyListState,
questListState: LazyListState,
locationListState: LazyListState,
startDestination: String = LEXICON_ROUTE
) {
CompositionLocalProvider(
@ -73,7 +76,8 @@ fun HomeNavHost(
tonalElevation = 0.dp,
) {
bottomBarItems.forEachIndexed { index, item ->
val selectedItem = remember { derivedStateOf { selectedIndex.value == index } }
val selectedItem =
remember { derivedStateOf { selectedIndex.value == index } }
NavigationBarItem(
selected = selectedItem.value,
onClick = {
@ -103,7 +107,8 @@ fun HomeNavHost(
startDestination = startDestination,
) {
composableLexicon(lazyListState = lexiconListState)
composableQuestList()
composableQuests(lazyListState = questListState)
composableLocations(lazyListState = locationListState)
}
}
}
@ -124,17 +129,23 @@ private fun rememberBottomBarItems(
navHostController: NavHostController,
): List<BottomBarItem> {
return remember(navHostController) {
val option = navHostController.pageOption()
listOf(
BottomBarItem(
icon = R.drawable.ic_outline_account_circle_24,
label = R.string.home_lexicon,
onClick = { navHostController.navigateToLexicon(navHostController.pageOption()) }
onClick = { navHostController.navigateToLexicon(option) }
),
BottomBarItem(
icon = R.drawable.ic_outline_scroll_24,
label = R.string.home_quest_log,
onClick = { navHostController.navigateToQuestList(option) }
),
BottomBarItem(
icon = R.drawable.ic_outline_map_24,
label = R.string.home_quest_log,
onClick = { navHostController.navigateToQuestList(navHostController.pageOption()) }
),
label = R.string.home_location,
onClick = { navHostController.navigateToLocation(option) }
)
)
}
}

View file

@ -28,7 +28,9 @@ fun ScreenNavHost(
navHostController: NavHostController = rememberAnimatedNavController(),
startDestination: String = AUTHENTICATION_ROUTE,
) {
val lexiconListState: LazyListState = rememberLazyListState()
val lexiconListState =rememberLazyListState()
val questListState = rememberLazyListState()
val locationListState = rememberLazyListState()
CompositionLocalProvider(
LocalScreenNavHost provides navHostController,
@ -38,7 +40,11 @@ fun ScreenNavHost(
startDestination = startDestination,
) {
composableAuthentication()
composableHome(lexiconListState)
composableHome(
lexiconListState = lexiconListState,
questListState = questListState,
locationListState = locationListState
)
composableLexiconDetail()
composableLexiconSearch()

View file

@ -0,0 +1,32 @@
package com.pixelized.rplexicon.ui.navigation.pages
import androidx.compose.foundation.lazy.LazyListState
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptionsBuilder
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
import com.pixelized.rplexicon.ui.navigation.animatedComposable
import com.pixelized.rplexicon.ui.screens.location.list.LocationScreen
private const val ROUTE = "locations"
const val LOCATION_ROUTE = ROUTE
fun NavGraphBuilder.composableLocations(
lazyListState: LazyListState,
) {
animatedComposable(
route = LOCATION_ROUTE,
animation = NavigationAnimation.NONE,
) {
LocationScreen(
lazyListState = lazyListState,
)
}
}
fun NavController.navigateToLocation(
option: NavOptionsBuilder.() -> Unit = {},
) {
navigate(route = ROUTE, builder = option)
}

View file

@ -1,5 +1,6 @@
package com.pixelized.rplexicon.ui.navigation.pages
import androidx.compose.foundation.lazy.LazyListState
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
@ -11,12 +12,14 @@ private const val ROUTE = "quests"
const val QUEST_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableQuestList() {
fun NavGraphBuilder.composableQuests(
lazyListState: LazyListState,
) {
animatedComposable(
route = QUEST_LIST_ROUTE,
animation = NavigationAnimation.NONE,
) {
QuestListScreen()
QuestListScreen(lazyListState = lazyListState)
}
}

View file

@ -14,12 +14,18 @@ const val HOME_ROUTE = ROUTE
fun NavGraphBuilder.composableHome(
lexiconListState: LazyListState,
questListState: LazyListState,
locationListState: LazyListState,
) {
animatedComposable(
route = HOME_ROUTE,
animation = NavigationAnimation.Fade,
) {
HomeNavHost(lexiconListState = lexiconListState)
HomeNavHost(
lexiconListState = lexiconListState,
questListState = questListState,
locationListState = locationListState
)
}
}

View file

@ -1,11 +1,7 @@
package com.pixelized.rplexicon.ui.screens.lexicon.list
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
@ -29,8 +25,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
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
@ -38,39 +32,23 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconSearch
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Default
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Permission
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Structure
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.cell
import com.pixelized.rplexicon.utilitary.extentions.lexicon
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@Stable
sealed class LexiconErrorUio {
@Stable
data class Permission(val intent: Intent) : LexiconErrorUio()
@Stable
object Structure : LexiconErrorUio()
@Stable
object Default : LexiconErrorUio()
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -111,9 +89,9 @@ fun LexiconScreen(
},
)
HandleError(
HandleFetchError(
errors = viewModel.error,
onLexiconPermissionGranted = {
onPermissionGranted = {
viewModel.fetchLexicon()
}
)
@ -216,36 +194,6 @@ private fun LexiconScreenContent(
}
}
@Composable
fun HandleError(
errors: SharedFlow<LexiconErrorUio>,
onLexiconPermissionGranted: suspend () -> Unit,
) {
val context = LocalContext.current
val snack = LocalSnack.current
val scope = rememberCoroutineScope()
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
scope.launch {
onLexiconPermissionGranted()
}
}
}
LaunchedEffect(key1 = "LexiconErrorManagement") {
errors.collect { error ->
when (error) {
is Permission -> launcher.launch(error.intent)
is Structure -> snack.showSnackbar(message = context.getString(R.string.error_structure))
is Default -> snack.showSnackbar(message = context.getString(R.string.error_generic))
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)

View file

@ -9,6 +9,7 @@ import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecovera
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.repository.LexiconRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
@ -27,8 +28,8 @@ class LexiconViewModel @Inject constructor(
private val _items = mutableStateOf<List<LexiconItemUio>>(emptyList())
val items: State<List<LexiconItemUio>> get() = _items
private val _error = MutableSharedFlow<LexiconErrorUio>()
val error: SharedFlow<LexiconErrorUio> get() = _error
private val _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
init {
viewModelScope.launch {
@ -77,15 +78,15 @@ class LexiconViewModel @Inject constructor(
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(LexiconErrorUio.Permission(intent = exception.intent))
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
} catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(LexiconErrorUio.Structure)
_error.emit(FetchErrorUio.Structure)
}
// default exception
catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(LexiconErrorUio.Default)
_error.emit(FetchErrorUio.Default)
}
// clean the laoding state
finally {

View file

@ -0,0 +1,114 @@
package com.pixelized.rplexicon.ui.screens.location.list
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.LOS_HOLLOW
import com.pixelized.rplexicon.utilitary.extentions.cell
import com.pixelized.rplexicon.utilitary.extentions.lexicon
import com.pixelized.rplexicon.utilitary.extentions.placeholder
@Stable
data class LocationItemUio(
val id: Int,
val title: String,
val placeholder: Boolean = false,
) {
companion object {
fun preview(
id: Int = 0,
title: String = "Daggerfall",
placeHolder: Boolean = false,
): LocationItemUio {
return LocationItemUio(
id = id,
title = title,
placeholder = placeHolder,
)
}
}
}
@Composable
fun LocationItem(
modifier: Modifier = Modifier,
item: LocationItemUio,
) {
val typography = MaterialTheme.lexicon.typography
Box(
modifier = modifier,
contentAlignment = Alignment.CenterStart,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = when (item.placeholder) {
true -> Modifier.placeholder { true }
else -> Modifier.alignByBaseline()
},
text = LOS_HOLLOW,
)
Text(
modifier = when (item.placeholder) {
true -> Modifier.placeholder { true }
else -> Modifier.alignByBaseline()
},
text = remember(item.placeholder) {
AnnotatedString(
text = item.title,
spanStyles = when (item.placeholder) {
true -> emptyList()
else -> listOf(
AnnotatedString.Range(
item = typography.dropCapMediumSpan,
start = 0,
end = 1,
)
)
},
)
},
)
}
}
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun QuestItemPreview(
@PreviewParameter(QuestItemPreviewProvider::class) preview: LocationItemUio,
) {
LexiconTheme {
Surface {
LocationItem(
modifier = Modifier.cell(),
item = preview,
)
}
}
}
private class QuestItemPreviewProvider : PreviewParameterProvider<LocationItemUio> {
override val values: Sequence<LocationItemUio> = sequenceOf(
LocationItemUio.preview(placeHolder = false),
LocationItemUio.preview(placeHolder = true),
)
}

View file

@ -0,0 +1,167 @@
package com.pixelized.rplexicon.ui.screens.location.list
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
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.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.cell
import com.pixelized.rplexicon.utilitary.extentions.lexicon
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LocationScreen(
viewModel: LocationViewModel = hiltViewModel(),
lazyListState: LazyListState,
) {
val scope = rememberCoroutineScope()
val screen = LocalScreenNavHost.current
val refresh = rememberPullRefreshState(
refreshing = false,
onRefresh = {
scope.launch {
viewModel.fetchLocation()
}
},
)
Surface {
LocationContent(
items = viewModel.items,
lazyColumnState = lazyListState,
refreshState = refresh,
refreshing = viewModel.isLoading,
onItem = {
// screen.navigateToQuestDetail(id = it.id)
},
)
HandleFetchError(
errors = viewModel.error,
onPermissionGranted = {
viewModel.fetchLocation()
}
)
}
}
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@Composable
private fun LocationContent(
modifier: Modifier = Modifier,
lazyColumnState: LazyListState,
refreshState: PullRefreshState,
refreshing: State<Boolean>,
items: State<List<LocationItemUio>>,
onItem: (LocationItemUio) -> Unit,
) {
Box(
modifier = modifier,
contentAlignment = Alignment.TopCenter,
) {
AnimatedContent(
targetState = items.value.isEmpty(),
transitionSpec = MaterialTheme.lexicon.animation.itemList,
label = "AnimatedLocations"
) { empty ->
when (empty) {
true -> LazyColumn(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state = refreshState),
state = lazyColumnState,
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
) {
items(count = 2) {
LocationItem(
modifier = Modifier.cell(),
item = LocationItemUio.preview(placeHolder = true),
)
}
}
else -> LazyColumn(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state = refreshState),
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,
)
}
}
}
}
Loader(
refreshState = refreshState,
refreshing = refreshing,
)
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun QuestListPreview() {
LexiconTheme {
Surface {
LocationContent(
modifier = Modifier.fillMaxSize(),
lazyColumnState = rememberLazyListState(),
refreshState = rememberPullRefreshState(
refreshing = false,
onRefresh = {},
),
refreshing = remember { mutableStateOf(false) },
items = remember {
mutableStateOf(
listOf(
LocationItemUio.preview(id = 0, title = "Daggerfall"),
LocationItemUio.preview(id = 1, title = "Athkatla"),
)
)
},
onItem = { },
)
}
}
}

View file

@ -0,0 +1,77 @@
package com.pixelized.rplexicon.ui.screens.location.list
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.repository.LocationRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class LocationViewModel @Inject constructor(
private val repository: LocationRepository,
) : ViewModel() {
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 _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
init {
viewModelScope.launch {
launch {
repository.data.collect { items ->
_items.value = items.map { item ->
LocationItemUio(
id = item.id,
title = item.name,
)
}
}
}
launch {
fetchLocation()
}
}
}
suspend fun fetchLocation() {
try {
_isLoading.value = true
repository.fetchLocation()
}
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
} catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
}
// default exception
catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Default)
}
// clean the laoding state
finally {
_isLoading.value = false
}
}
companion object {
private const val TAG = "LocationViewModel"
}
}

View file

@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestDetail
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@ -38,9 +39,9 @@ import kotlinx.coroutines.launch
@Composable
fun QuestListScreen(
viewModel: QuestListViewModel = hiltViewModel(),
lazyListState: LazyListState,
) {
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val screen = LocalScreenNavHost.current
val refresh = rememberPullRefreshState(
@ -62,6 +63,13 @@ fun QuestListScreen(
screen.navigateToQuestDetail(id = it.id)
},
)
HandleFetchError(
errors = viewModel.error,
onPermissionGranted = {
viewModel.fetchQuests()
}
)
}
}
@ -110,7 +118,7 @@ private fun QuestListContent(
items(
items = items.value,
key = { it.id },
contentType = { "Lexicon" },
contentType = { "Quest" },
) {
QuestItem(
modifier = Modifier

View file

@ -7,8 +7,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.repository.QuestRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -23,8 +26,8 @@ class QuestListViewModel @Inject constructor(
private val _items = mutableStateOf<List<QuestItemUio>>(emptyList())
val items: State<List<QuestItemUio>> get() = _items
// private val _error = MutableSharedFlow<LexiconErrorUio>()
// val error: SharedFlow<LexiconErrorUio> get() = _error
private val _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
init {
viewModelScope.launch {
@ -53,15 +56,15 @@ class QuestListViewModel @Inject constructor(
// user need to accept OAuth2 permission.
catch (exception: UserRecoverableAuthIOException) {
Log.e(TAG, exception.message, exception)
// _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
} catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
// _error.emit(LexiconErrorUio.Structure)
_error.emit(FetchErrorUio.Structure)
}
// default exception
catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
// _error.emit(LexiconErrorUio.Default)
_error.emit(FetchErrorUio.Default)
}
// clean the laoding state
finally {

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8,2C6.355,2 5,3.355 5,5L5,16L2,16L2,19C2,20.645 3.355,22 5,22L14,22L15,22C16.645,22 18,20.645 18,19L18,8L22,8L22,5C22,3.355 20.645,2 19,2L8,2zM8,4L16.188,4C16.074,4.315 16,4.648 16,5L16,19C16,19.56 15.572,19.991 15.014,19.998L15.014,19.986C14.45,19.994 14.008,19.563 14,19L13.973,16.004L13,16.014L13,16L7,16L7,5C7,4.435 7.435,4 8,4zM19,4C19.565,4 20,4.435 20,5L20,6L18,6L18,5C18,4.435 18.435,4 19,4zM4,18L11.99,18L12,19.025L12,19.027C12.005,19.37 12.081,19.694 12.193,20L5,20C4.435,20 4,19.565 4,19L4,18z"/>
</vector>

View file

@ -30,6 +30,7 @@
<string name="home_lexicon">Lexique</string>
<string name="home_quest_log">Journal de quêtes</string>
<string name="home_location">Cartes</string>
<string name="lexicon_search">Rechercher</string>

View file

@ -30,6 +30,7 @@
<string name="home_lexicon">Lexicon</string>
<string name="home_quest_log">Quest logs</string>
<string name="home_location">Location</string>
<string name="lexicon_search">Search</string>