add map list tab
This commit is contained in:
		
							parent
							
								
									87a1471efe
								
							
						
					
					
						commit
						57d1273435
					
				
					 17 changed files with 527 additions and 82 deletions
				
			
		| 
						 | 
					@ -32,6 +32,7 @@ class LocationParser @Inject constructor() {
 | 
				
			||||||
                            sheetIndex = index,
 | 
					                            sheetIndex = index,
 | 
				
			||||||
                            name = name,
 | 
					                            name = name,
 | 
				
			||||||
                            uri = uri,
 | 
					                            uri = uri,
 | 
				
			||||||
 | 
					                            marquees = emptyList(),
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        null
 | 
					                        null
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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.layout.navigationBarsPadding
 | 
					 | 
				
			||||||
import androidx.compose.foundation.layout.padding
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
import androidx.compose.foundation.lazy.LazyListState
 | 
					import androidx.compose.foundation.lazy.LazyListState
 | 
				
			||||||
import androidx.compose.material3.BottomAppBar
 | 
					import androidx.compose.material3.BottomAppBar
 | 
				
			||||||
| 
						 | 
					@ -33,8 +32,10 @@ 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_ROUTE
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon
 | 
					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.navigateToLexicon
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.navigation.pages.navigateToLocation
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.pages.navigateToQuestList
 | 
					import com.pixelized.rplexicon.ui.navigation.pages.navigateToQuestList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
 | 
					val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
 | 
				
			||||||
| 
						 | 
					@ -44,9 +45,11 @@ val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
 | 
				
			||||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
 | 
					@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun HomeNavHost(
 | 
					fun HomeNavHost(
 | 
				
			||||||
    lexiconListState: LazyListState,
 | 
					 | 
				
			||||||
    navHostController: NavHostController = rememberAnimatedNavController(),
 | 
					    navHostController: NavHostController = rememberAnimatedNavController(),
 | 
				
			||||||
    bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController = navHostController),
 | 
					    bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController),
 | 
				
			||||||
 | 
					    lexiconListState: LazyListState,
 | 
				
			||||||
 | 
					    questListState: LazyListState,
 | 
				
			||||||
 | 
					    locationListState: LazyListState,
 | 
				
			||||||
    startDestination: String = LEXICON_ROUTE
 | 
					    startDestination: String = LEXICON_ROUTE
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    CompositionLocalProvider(
 | 
					    CompositionLocalProvider(
 | 
				
			||||||
| 
						 | 
					@ -73,7 +76,8 @@ fun HomeNavHost(
 | 
				
			||||||
                    tonalElevation = 0.dp,
 | 
					                    tonalElevation = 0.dp,
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    bottomBarItems.forEachIndexed { index, item ->
 | 
					                    bottomBarItems.forEachIndexed { index, item ->
 | 
				
			||||||
                        val selectedItem = remember { derivedStateOf { selectedIndex.value == index } }
 | 
					                        val selectedItem =
 | 
				
			||||||
 | 
					                            remember { derivedStateOf { selectedIndex.value == index } }
 | 
				
			||||||
                        NavigationBarItem(
 | 
					                        NavigationBarItem(
 | 
				
			||||||
                            selected = selectedItem.value,
 | 
					                            selected = selectedItem.value,
 | 
				
			||||||
                            onClick = {
 | 
					                            onClick = {
 | 
				
			||||||
| 
						 | 
					@ -103,7 +107,8 @@ fun HomeNavHost(
 | 
				
			||||||
                        startDestination = startDestination,
 | 
					                        startDestination = startDestination,
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                        composableLexicon(lazyListState = lexiconListState)
 | 
					                        composableLexicon(lazyListState = lexiconListState)
 | 
				
			||||||
                        composableQuestList()
 | 
					                        composableQuests(lazyListState = questListState)
 | 
				
			||||||
 | 
					                        composableLocations(lazyListState = locationListState)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -124,17 +129,23 @@ private fun rememberBottomBarItems(
 | 
				
			||||||
    navHostController: NavHostController,
 | 
					    navHostController: NavHostController,
 | 
				
			||||||
): List<BottomBarItem> {
 | 
					): List<BottomBarItem> {
 | 
				
			||||||
    return remember(navHostController) {
 | 
					    return remember(navHostController) {
 | 
				
			||||||
 | 
					        val option = navHostController.pageOption()
 | 
				
			||||||
        listOf(
 | 
					        listOf(
 | 
				
			||||||
            BottomBarItem(
 | 
					            BottomBarItem(
 | 
				
			||||||
                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(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(
 | 
					            BottomBarItem(
 | 
				
			||||||
                icon = R.drawable.ic_outline_map_24,
 | 
					                icon = R.drawable.ic_outline_map_24,
 | 
				
			||||||
                label = R.string.home_quest_log,
 | 
					                label = R.string.home_location,
 | 
				
			||||||
                onClick = { navHostController.navigateToQuestList(navHostController.pageOption()) }
 | 
					                onClick = { navHostController.navigateToLocation(option) }
 | 
				
			||||||
            ),
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,9 @@ fun ScreenNavHost(
 | 
				
			||||||
    navHostController: NavHostController = rememberAnimatedNavController(),
 | 
					    navHostController: NavHostController = rememberAnimatedNavController(),
 | 
				
			||||||
    startDestination: String = AUTHENTICATION_ROUTE,
 | 
					    startDestination: String = AUTHENTICATION_ROUTE,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val lexiconListState: LazyListState = rememberLazyListState()
 | 
					    val lexiconListState =rememberLazyListState()
 | 
				
			||||||
 | 
					    val questListState = rememberLazyListState()
 | 
				
			||||||
 | 
					    val locationListState = rememberLazyListState()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CompositionLocalProvider(
 | 
					    CompositionLocalProvider(
 | 
				
			||||||
        LocalScreenNavHost provides navHostController,
 | 
					        LocalScreenNavHost provides navHostController,
 | 
				
			||||||
| 
						 | 
					@ -38,7 +40,11 @@ fun ScreenNavHost(
 | 
				
			||||||
            startDestination = startDestination,
 | 
					            startDestination = startDestination,
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            composableAuthentication()
 | 
					            composableAuthentication()
 | 
				
			||||||
            composableHome(lexiconListState)
 | 
					            composableHome(
 | 
				
			||||||
 | 
					                lexiconListState = lexiconListState,
 | 
				
			||||||
 | 
					                questListState = questListState,
 | 
				
			||||||
 | 
					                locationListState = locationListState
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            composableLexiconDetail()
 | 
					            composableLexiconDetail()
 | 
				
			||||||
            composableLexiconSearch()
 | 
					            composableLexiconSearch()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
package com.pixelized.rplexicon.ui.navigation.pages
 | 
					package com.pixelized.rplexicon.ui.navigation.pages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.LazyListState
 | 
				
			||||||
import androidx.navigation.NavGraphBuilder
 | 
					import androidx.navigation.NavGraphBuilder
 | 
				
			||||||
import androidx.navigation.NavHostController
 | 
					import androidx.navigation.NavHostController
 | 
				
			||||||
import androidx.navigation.NavOptionsBuilder
 | 
					import androidx.navigation.NavOptionsBuilder
 | 
				
			||||||
| 
						 | 
					@ -11,12 +12,14 @@ private const val ROUTE = "quests"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const val QUEST_LIST_ROUTE = ROUTE
 | 
					const val QUEST_LIST_ROUTE = ROUTE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun NavGraphBuilder.composableQuestList() {
 | 
					fun NavGraphBuilder.composableQuests(
 | 
				
			||||||
 | 
					    lazyListState: LazyListState,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    animatedComposable(
 | 
					    animatedComposable(
 | 
				
			||||||
        route = QUEST_LIST_ROUTE,
 | 
					        route = QUEST_LIST_ROUTE,
 | 
				
			||||||
        animation = NavigationAnimation.NONE,
 | 
					        animation = NavigationAnimation.NONE,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        QuestListScreen()
 | 
					        QuestListScreen(lazyListState = lazyListState)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,18 @@ const val HOME_ROUTE = ROUTE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun NavGraphBuilder.composableHome(
 | 
					fun NavGraphBuilder.composableHome(
 | 
				
			||||||
    lexiconListState: LazyListState,
 | 
					    lexiconListState: LazyListState,
 | 
				
			||||||
 | 
					    questListState: LazyListState,
 | 
				
			||||||
 | 
					    locationListState: LazyListState,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    animatedComposable(
 | 
					    animatedComposable(
 | 
				
			||||||
        route = HOME_ROUTE,
 | 
					        route = HOME_ROUTE,
 | 
				
			||||||
        animation = NavigationAnimation.Fade,
 | 
					        animation = NavigationAnimation.Fade,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        HomeNavHost(lexiconListState = lexiconListState)
 | 
					        HomeNavHost(
 | 
				
			||||||
 | 
					            lexiconListState = lexiconListState,
 | 
				
			||||||
 | 
					            questListState = questListState,
 | 
				
			||||||
 | 
					            locationListState = locationListState
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,7 @@
 | 
				
			||||||
package com.pixelized.rplexicon.ui.screens.lexicon.list
 | 
					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_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					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.AnimatedContent
 | 
				
			||||||
import androidx.compose.animation.AnimatedVisibility
 | 
					import androidx.compose.animation.AnimatedVisibility
 | 
				
			||||||
import androidx.compose.animation.ExperimentalAnimationApi
 | 
					import androidx.compose.animation.ExperimentalAnimationApi
 | 
				
			||||||
| 
						 | 
					@ -29,8 +25,6 @@ import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
import androidx.compose.material3.Surface
 | 
					import androidx.compose.material3.Surface
 | 
				
			||||||
import androidx.compose.material3.Text
 | 
					import androidx.compose.material3.Text
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.LaunchedEffect
 | 
					 | 
				
			||||||
import androidx.compose.runtime.Stable
 | 
					 | 
				
			||||||
import androidx.compose.runtime.State
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.derivedStateOf
 | 
					import androidx.compose.runtime.derivedStateOf
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
| 
						 | 
					@ -38,39 +32,23 @@ import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
					import androidx.compose.runtime.rememberCoroutineScope
 | 
				
			||||||
import androidx.compose.ui.Alignment
 | 
					import androidx.compose.ui.Alignment
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.platform.LocalContext
 | 
					 | 
				
			||||||
import androidx.compose.ui.res.painterResource
 | 
					import androidx.compose.ui.res.painterResource
 | 
				
			||||||
import androidx.compose.ui.res.stringResource
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
					import androidx.hilt.navigation.compose.hiltViewModel
 | 
				
			||||||
import com.pixelized.rplexicon.LocalSnack
 | 
					 | 
				
			||||||
import com.pixelized.rplexicon.R
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
 | 
					import com.pixelized.rplexicon.ui.composable.FloatingActionButton
 | 
				
			||||||
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.navigation.LocalScreenNavHost
 | 
					import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconSearch
 | 
					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.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
 | 
				
			||||||
import kotlinx.coroutines.flow.SharedFlow
 | 
					 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					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)
 | 
					@OptIn(ExperimentalMaterialApi::class)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
| 
						 | 
					@ -111,9 +89,9 @@ fun LexiconScreen(
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        HandleError(
 | 
					        HandleFetchError(
 | 
				
			||||||
            errors = viewModel.error,
 | 
					            errors = viewModel.error,
 | 
				
			||||||
            onLexiconPermissionGranted = {
 | 
					            onPermissionGranted = {
 | 
				
			||||||
                viewModel.fetchLexicon()
 | 
					                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)
 | 
					@OptIn(ExperimentalMaterialApi::class)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
					@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecovera
 | 
				
			||||||
import com.pixelized.rplexicon.R
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.model.Lexicon
 | 
					import com.pixelized.rplexicon.model.Lexicon
 | 
				
			||||||
import com.pixelized.rplexicon.repository.LexiconRepository
 | 
					import com.pixelized.rplexicon.repository.LexiconRepository
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
					import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
				
			||||||
import dagger.hilt.android.lifecycle.HiltViewModel
 | 
					import dagger.hilt.android.lifecycle.HiltViewModel
 | 
				
			||||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
					import kotlinx.coroutines.flow.MutableSharedFlow
 | 
				
			||||||
| 
						 | 
					@ -27,8 +28,8 @@ class LexiconViewModel @Inject constructor(
 | 
				
			||||||
    private val _items = mutableStateOf<List<LexiconItemUio>>(emptyList())
 | 
					    private val _items = mutableStateOf<List<LexiconItemUio>>(emptyList())
 | 
				
			||||||
    val items: State<List<LexiconItemUio>> get() = _items
 | 
					    val items: State<List<LexiconItemUio>> get() = _items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _error = MutableSharedFlow<LexiconErrorUio>()
 | 
					    private val _error = MutableSharedFlow<FetchErrorUio>()
 | 
				
			||||||
    val error: SharedFlow<LexiconErrorUio> get() = _error
 | 
					    val error: SharedFlow<FetchErrorUio> get() = _error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
        viewModelScope.launch {
 | 
					        viewModelScope.launch {
 | 
				
			||||||
| 
						 | 
					@ -77,15 +78,15 @@ class LexiconViewModel @Inject constructor(
 | 
				
			||||||
        // user need to accept OAuth2 permission.
 | 
					        // user need to accept OAuth2 permission.
 | 
				
			||||||
        catch (exception: UserRecoverableAuthIOException) {
 | 
					        catch (exception: UserRecoverableAuthIOException) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
            _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
 | 
					            _error.emit(FetchErrorUio.Permission(intent = exception.intent))
 | 
				
			||||||
        } catch (exception: IncompatibleSheetStructure) {
 | 
					        } catch (exception: IncompatibleSheetStructure) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
            _error.emit(LexiconErrorUio.Structure)
 | 
					            _error.emit(FetchErrorUio.Structure)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // default exception
 | 
					        // default exception
 | 
				
			||||||
        catch (exception: Exception) {
 | 
					        catch (exception: Exception) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
            _error.emit(LexiconErrorUio.Default)
 | 
					            _error.emit(FetchErrorUio.Default)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // clean the laoding state
 | 
					        // clean the laoding state
 | 
				
			||||||
        finally {
 | 
					        finally {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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 = { },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
					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.navigation.LocalScreenNavHost
 | 
					import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestDetail
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestDetail
 | 
				
			||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
					import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
				
			||||||
| 
						 | 
					@ -38,9 +39,9 @@ import kotlinx.coroutines.launch
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun QuestListScreen(
 | 
					fun QuestListScreen(
 | 
				
			||||||
    viewModel: QuestListViewModel = hiltViewModel(),
 | 
					    viewModel: QuestListViewModel = hiltViewModel(),
 | 
				
			||||||
 | 
					    lazyListState: LazyListState,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val scope = rememberCoroutineScope()
 | 
					    val scope = rememberCoroutineScope()
 | 
				
			||||||
    val lazyListState = rememberLazyListState()
 | 
					 | 
				
			||||||
    val screen = LocalScreenNavHost.current
 | 
					    val screen = LocalScreenNavHost.current
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val refresh = rememberPullRefreshState(
 | 
					    val refresh = rememberPullRefreshState(
 | 
				
			||||||
| 
						 | 
					@ -62,6 +63,13 @@ fun QuestListScreen(
 | 
				
			||||||
                screen.navigateToQuestDetail(id = it.id)
 | 
					                screen.navigateToQuestDetail(id = it.id)
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        HandleFetchError(
 | 
				
			||||||
 | 
					            errors = viewModel.error,
 | 
				
			||||||
 | 
					            onPermissionGranted = {
 | 
				
			||||||
 | 
					                viewModel.fetchQuests()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,7 +118,7 @@ private fun QuestListContent(
 | 
				
			||||||
                    items(
 | 
					                    items(
 | 
				
			||||||
                        items = items.value,
 | 
					                        items = items.value,
 | 
				
			||||||
                        key = { it.id },
 | 
					                        key = { it.id },
 | 
				
			||||||
                        contentType = { "Lexicon" },
 | 
					                        contentType = { "Quest" },
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                        QuestItem(
 | 
					                        QuestItem(
 | 
				
			||||||
                            modifier = Modifier
 | 
					                            modifier = Modifier
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,11 @@ import androidx.lifecycle.ViewModel
 | 
				
			||||||
import androidx.lifecycle.viewModelScope
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
 | 
					import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
 | 
				
			||||||
import com.pixelized.rplexicon.repository.QuestRepository
 | 
					import com.pixelized.rplexicon.repository.QuestRepository
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
					import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
				
			||||||
import dagger.hilt.android.lifecycle.HiltViewModel
 | 
					import dagger.hilt.android.lifecycle.HiltViewModel
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.MutableSharedFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
import javax.inject.Inject
 | 
					import javax.inject.Inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,8 +26,8 @@ class QuestListViewModel @Inject constructor(
 | 
				
			||||||
    private val _items = mutableStateOf<List<QuestItemUio>>(emptyList())
 | 
					    private val _items = mutableStateOf<List<QuestItemUio>>(emptyList())
 | 
				
			||||||
    val items: State<List<QuestItemUio>> get() = _items
 | 
					    val items: State<List<QuestItemUio>> get() = _items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    private val _error = MutableSharedFlow<LexiconErrorUio>()
 | 
					    private val _error = MutableSharedFlow<FetchErrorUio>()
 | 
				
			||||||
//    val error: SharedFlow<LexiconErrorUio> get() = _error
 | 
					    val error: SharedFlow<FetchErrorUio> get() = _error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
        viewModelScope.launch {
 | 
					        viewModelScope.launch {
 | 
				
			||||||
| 
						 | 
					@ -53,15 +56,15 @@ class QuestListViewModel @Inject constructor(
 | 
				
			||||||
        // user need to accept OAuth2 permission.
 | 
					        // user need to accept OAuth2 permission.
 | 
				
			||||||
        catch (exception: UserRecoverableAuthIOException) {
 | 
					        catch (exception: UserRecoverableAuthIOException) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
//                _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
 | 
					            _error.emit(FetchErrorUio.Permission(intent = exception.intent))
 | 
				
			||||||
        } catch (exception: IncompatibleSheetStructure) {
 | 
					        } catch (exception: IncompatibleSheetStructure) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
//                _error.emit(LexiconErrorUio.Structure)
 | 
					            _error.emit(FetchErrorUio.Structure)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // default exception
 | 
					        // default exception
 | 
				
			||||||
        catch (exception: Exception) {
 | 
					        catch (exception: Exception) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
//                _error.emit(LexiconErrorUio.Default)
 | 
					            _error.emit(FetchErrorUio.Default)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // clean the laoding state
 | 
					        // clean the laoding state
 | 
				
			||||||
        finally {
 | 
					        finally {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/ic_outline_scroll_24.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/ic_outline_scroll_24.xml
									
										
									
									
									
										Normal 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>
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="home_lexicon">Lexique</string>
 | 
					    <string name="home_lexicon">Lexique</string>
 | 
				
			||||||
    <string name="home_quest_log">Journal de quêtes</string>
 | 
					    <string name="home_quest_log">Journal de quêtes</string>
 | 
				
			||||||
 | 
					    <string name="home_location">Cartes</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="lexicon_search">Rechercher</string>
 | 
					    <string name="lexicon_search">Rechercher</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="home_lexicon">Lexicon</string>
 | 
					    <string name="home_lexicon">Lexicon</string>
 | 
				
			||||||
    <string name="home_quest_log">Quest logs</string>
 | 
					    <string name="home_quest_log">Quest logs</string>
 | 
				
			||||||
 | 
					    <string name="home_location">Location</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="lexicon_search">Search</string>
 | 
					    <string name="lexicon_search">Search</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue