From 5269d9883d02a68fa19a8a0622ff11a88842be39 Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Mon, 15 Apr 2024 09:46:27 +0200 Subject: [PATCH] Rework the landing page layout --- .../rplexicon/business/SearchUseCase.kt | 8 +- .../ui/composable/error/FetchErrorUio.kt | 32 +- .../ui/composable/form/TextFieldAppBar.kt | 2 +- .../ui/composable/rememberAnimatedShadow.kt | 31 ++ .../rplexicon/ui/navigation/HomeNavHost.kt | 194 ----------- .../rplexicon/ui/navigation/ScreenNavHost.kt | 26 +- .../ui/navigation/screens/ComposableHome.kt | 36 -- .../{pages => screens}/ComposableLexicon.kt | 13 +- .../{pages => screens}/ComposableLocation.kt | 13 +- .../{pages => screens}/ComposableQuestList.kt | 13 +- .../authentication/AuthenticationScreen.kt | 7 +- .../composable/actions/GenericHeader.kt | 2 +- .../composable/actions/SpellHeader.kt | 2 +- .../composable/character/DeathHeader.kt | 4 +- .../character/pages/actions/SkillDetail.kt | 2 +- .../pages/alteration/AlterationDetail.kt | 2 +- .../pages/alteration/AlterationViewModel.kt | 2 +- .../ui/screens/landing/LandingItem.kt | 110 +++--- .../ui/screens/landing/LandingScreen.kt | 317 ++++++++++++++---- .../lexicon/detail/LexiconDetailScreen.kt | 18 +- .../ui/screens/lexicon/list/LexiconItem.kt | 16 +- .../ui/screens/lexicon/list/LexiconScreen.kt | 164 +++++---- .../screens/lexicon/list/LexiconViewModel.kt | 2 +- .../location/detail/LocationDetailScreen.kt | 20 +- .../ui/screens/location/detail/MarqueeItem.kt | 2 +- .../ui/screens/location/list/LocationItem.kt | 2 +- .../screens/location/list/LocationScreen.kt | 158 +++++---- .../location/list/LocationViewModel.kt | 2 +- .../screens/quest/detail/QuestDetailScreen.kt | 20 +- .../ui/screens/quest/list/QuestItem.kt | 2 +- .../ui/screens/quest/list/QuestListScreen.kt | 166 +++++---- .../screens/quest/list/QuestListViewModel.kt | 2 +- .../rplexicon/ui/screens/rolls/RollOverlay.kt | 6 +- .../ui/screens/rolls/RollOverlayViewModel.kt | 2 +- .../ui/screens/rolls/composable/RollDice.kt | 28 +- .../ui/screens/search/SearchFilter.kt | 6 +- .../screens/search/item/LexiconSearchItem.kt | 2 +- .../ui/screens/spell/SpellDetailScreen.kt | 2 +- .../ui/screens/summary/SummaryScreen.kt | 2 +- .../pages/statistic/StatisticViewModel.kt | 4 +- .../ui/theme/typography/LexiconTypography.kt | 134 ++++++-- .../utilitary/AnnotatedStringHelper.kt | 13 + app/src/main/res/drawable/im_naderius.webp | Bin 0 -> 28700 bytes app/src/main/res/values-fr/strings.xml | 104 +++--- app/src/main/res/values/strings.xml | 118 ++++--- 45 files changed, 999 insertions(+), 812 deletions(-) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableHome.kt rename app/src/main/java/com/pixelized/rplexicon/ui/navigation/{pages => screens}/ComposableLexicon.kt (67%) rename app/src/main/java/com/pixelized/rplexicon/ui/navigation/{pages => screens}/ComposableLocation.kt (66%) rename app/src/main/java/com/pixelized/rplexicon/ui/navigation/{pages => screens}/ComposableQuestList.kt (66%) create mode 100644 app/src/main/res/drawable/im_naderius.webp diff --git a/app/src/main/java/com/pixelized/rplexicon/business/SearchUseCase.kt b/app/src/main/java/com/pixelized/rplexicon/business/SearchUseCase.kt index b5859ef..ac945c4 100644 --- a/app/src/main/java/com/pixelized/rplexicon/business/SearchUseCase.kt +++ b/app/src/main/java/com/pixelized/rplexicon/business/SearchUseCase.kt @@ -68,7 +68,7 @@ class SearchUseCase @Inject constructor( name = annotate( text = item.name, highlightRegex styleWith typography.search.titleHighlight, - dropCapRegex styleWith typography.titleMediumDropCap, + dropCapRegex styleWith typography.dropCap.titleMedium, ), diminutive = nullableAnnotate( text = item.diminutive, @@ -186,7 +186,7 @@ class SearchUseCase @Inject constructor( title = annotate( text = item.title, highlightRegex styleWith typography.search.titleHighlight, - dropCapRegex styleWith typography.titleMediumDropCap, + dropCapRegex styleWith typography.dropCap.titleMedium, ), owner = entry?.questGiverName?.let { AnnotatedString( @@ -269,7 +269,7 @@ class SearchUseCase @Inject constructor( title = annotate( text = item.name, highlightRegex styleWith typography.search.titleHighlight, - dropCapRegex styleWith typography.titleMediumDropCap, + dropCapRegex styleWith typography.dropCap.titleMedium, ), description = item.description?.let { extractSentence.find(it) }?.let { AnnotatedString( @@ -344,7 +344,7 @@ class SearchUseCase @Inject constructor( name = annotate( text = item.name, highlightRegex styleWith typography.search.titleHighlight, - dropCapRegex styleWith typography.titleMediumDropCap, + dropCapRegex styleWith typography.dropCap.titleMedium, ), translated = nullableAnnotate( text = itemDescription?.original, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt index 1d800ae..1f59559 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt @@ -48,30 +48,30 @@ fun HandleFetchError( errors: Flow, onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snackHost, error -> val messageResources = when (error.type) { - FetchErrorUio.Structure.Type.UNKNOWN -> R.string.error_structure_unknowed - FetchErrorUio.Structure.Type.ACTION -> R.string.error_structure_action - FetchErrorUio.Structure.Type.ALTERATION -> R.string.error_structure_alteration - FetchErrorUio.Structure.Type.CHARACTER -> R.string.error_structure_character - FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error_structure_description - FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error_structure_equipment - FetchErrorUio.Structure.Type.INVENTORY -> R.string.error_structure_inventory - FetchErrorUio.Structure.Type.OBJECT -> R.string.error_structure_objects - FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill - FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell - FetchErrorUio.Structure.Type.CATEGORY_ORDER -> R.string.error_structure_category_order - FetchErrorUio.Structure.Type.LEXICON -> R.string.error_structure_lexicon - FetchErrorUio.Structure.Type.LOCATION -> R.string.error_structure_location - FetchErrorUio.Structure.Type.QUEST -> R.string.error_structure_quest + FetchErrorUio.Structure.Type.UNKNOWN -> R.string.error__structure_unknowed + FetchErrorUio.Structure.Type.ACTION -> R.string.error__structure_action + FetchErrorUio.Structure.Type.ALTERATION -> R.string.error__structure_alteration + FetchErrorUio.Structure.Type.CHARACTER -> R.string.error__structure_character + FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error__structure_description + FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error__structure_equipment + FetchErrorUio.Structure.Type.INVENTORY -> R.string.error__structure_inventory + FetchErrorUio.Structure.Type.OBJECT -> R.string.error__structure_objects + FetchErrorUio.Structure.Type.SKILL -> R.string.error__structure_skill + FetchErrorUio.Structure.Type.SPELL -> R.string.error__structure_spell + FetchErrorUio.Structure.Type.CATEGORY_ORDER -> R.string.error__structure_category_order + FetchErrorUio.Structure.Type.LEXICON -> R.string.error__structure_lexicon + FetchErrorUio.Structure.Type.LOCATION -> R.string.error__structure_location + FetchErrorUio.Structure.Type.QUEST -> R.string.error__structure_quest } snackHost.showSnackbar(message = context.getString(messageResources)) }, onFirebaseError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Firebase) -> Unit = { context, snackHost, error -> snackHost.showSnackbar( - message = error.exception.localizedMessage ?: context.getString(R.string.error_generic) + message = error.exception.localizedMessage ?: context.getString(R.string.error__generic) ) }, onDefaultError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Default) -> Unit = { context, snackHost, _ -> - snackHost.showSnackbar(message = context.getString(R.string.error_generic)) + snackHost.showSnackbar(message = context.getString(R.string.error__generic)) }, ) { val context = LocalContext.current diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextFieldAppBar.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextFieldAppBar.kt index a2267fe..c9a2611 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextFieldAppBar.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextFieldAppBar.kt @@ -128,7 +128,7 @@ private fun TextFieldPreview( .fillMaxWidth() .padding(all = 8.dp), field = TextFieldUio( - label = R.string.lexicon_search, + label = R.string.default_search_label, value = remember { mutableStateOf(preview) }, ), onBack = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt new file mode 100644 index 0000000..ba7649b --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt @@ -0,0 +1,31 @@ +package com.pixelized.rplexicon.ui.composable + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun rememberAnimatedShadow( + lazyListState: LazyListState, + rest: Dp = 0.dp, + target: Dp = 4.dp, +): State { + val shadowTarget = remember(lazyListState) { + derivedStateOf { + if (lazyListState.firstVisibleItemScrollOffset > 0 || lazyListState.firstVisibleItemIndex != 0) { + target + } else { + rest + } + } + } + return animateDpAsState( + targetValue = shadowTarget.value, + label = "animated shadow", + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt deleted file mode 100644 index 831f88a..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt +++ /dev/null @@ -1,194 +0,0 @@ -package com.pixelized.rplexicon.ui.navigation - -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import com.pixelized.rplexicon.LocalSnack -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.navigation.pages.LEXICON_LIST_ROUTE -import com.pixelized.rplexicon.ui.navigation.pages.LOCATION_LIST_ROUTE -import com.pixelized.rplexicon.ui.navigation.pages.QUEST_LIST_ROUTE -import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon -import com.pixelized.rplexicon.ui.navigation.pages.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 -import com.pixelized.rplexicon.ui.navigation.screens.navigateToSummary -import kotlinx.coroutines.launch - -val LocalPageNavHost = staticCompositionLocalOf { - error("LocalScreenNavHost not ready") -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun HomeNavHost( - navHostController: NavHostController = rememberNavController(), - bottomBarItems: List = rememberBottomBarItems(navHostController), - startDestination: String = LEXICON_LIST_ROUTE, - lexiconListState: LazyListState, - questListState: LazyListState, - locationListState: LazyListState, -) { - val screen = LocalScreenNavHost.current - val scope = rememberCoroutineScope() - - CompositionLocalProvider( - LocalSnack provides remember { SnackbarHostState() }, - ) { - val backStackEntry = navHostController.currentBackStackEntryAsState() - - Scaffold( - topBar = { - TopAppBar( - modifier = Modifier.shadow(elevation = 4.dp), - title = { - Text(text = stringResource(id = R.string.app_name)) - }, - actions = { - IconButton( - onClick = { screen.navigateToSummary() }, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_crowned_skull_24), - contentDescription = null, - ) - } - } - ) - }, - snackbarHost = { - SnackbarHost( - hostState = LocalSnack.current, - ) - }, - bottomBar = { - NavigationBar( - containerColor = MaterialTheme.colorScheme.surface, - tonalElevation = 0.dp, - ) { - bottomBarItems.forEachIndexed { index, item -> - val selected by remember { - derivedStateOf { - item.route == backStackEntry.value?.destination?.route - } - } - NavigationBarItem( - selected = selected, - onClick = { - if (selected) { - scope.launch { - when (index) { - 0 -> lexiconListState.animateScrollToItem(index = 0) - 1 -> questListState.animateScrollToItem(index = 0) - 2 -> locationListState.animateScrollToItem(index = 0) - } - } - } else { - item.onClick.invoke() - } - }, - label = { - Text( - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - text = stringResource(id = item.label) - ) - }, - icon = { - Icon( - painter = painterResource(id = item.icon), - contentDescription = "", - ) - } - ) - } - } - }, - content = { padding -> - CompositionLocalProvider( - LocalPageNavHost provides navHostController, - ) { - NavHost( - modifier = Modifier.padding(padding), - navController = navHostController, - startDestination = startDestination, - ) { - composableLexicon(lazyListState = lexiconListState) - composableQuests(lazyListState = questListState) - composableLocations(lazyListState = locationListState) - } - } - } - ) - } -} - -@Stable -class BottomBarItem( - val route: String, - val icon: Int, - val label: Int, - val onClick: () -> Unit, -) - -@Composable -@Stable -private fun rememberBottomBarItems( - navHostController: NavHostController, -): List { - return remember(navHostController) { - val option = navHostController.pageOption() - listOf( - BottomBarItem( - route = LEXICON_LIST_ROUTE, - icon = R.drawable.ic_visored_helm_24, - label = R.string.home_lexicon, - onClick = { navHostController.navigateToLexicon(option) } - ), - BottomBarItem( - route = QUEST_LIST_ROUTE, - icon = R.drawable.ic_scroll_unfurled_24, - label = R.string.home_quest_log, - onClick = { navHostController.navigateToQuestList(option) } - ), - BottomBarItem( - route = LOCATION_LIST_ROUTE, - icon = R.drawable.ic_treasure_map_24, - label = R.string.home_location, - onClick = { navHostController.navigateToLocation(option) } - ) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt index 96e903f..151f04c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt @@ -1,6 +1,5 @@ package com.pixelized.rplexicon.ui.navigation -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf @@ -9,18 +8,20 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController +import com.pixelized.rplexicon.ui.navigation.screens.composableLexicon +import com.pixelized.rplexicon.ui.navigation.screens.composableLocations +import com.pixelized.rplexicon.ui.navigation.screens.composableQuests import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet -import com.pixelized.rplexicon.ui.navigation.screens.composableHome import com.pixelized.rplexicon.ui.navigation.screens.composableLanding import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconDetail -import com.pixelized.rplexicon.ui.navigation.screens.composableSearch import com.pixelized.rplexicon.ui.navigation.screens.composableLocationDetail import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail +import com.pixelized.rplexicon.ui.navigation.screens.composableSearch import com.pixelized.rplexicon.ui.navigation.screens.composableSpellDetail import com.pixelized.rplexicon.ui.navigation.screens.composableSummary -import com.pixelized.rplexicon.ui.navigation.screens.navigateToHome +import com.pixelized.rplexicon.ui.navigation.screens.navigateToLanding val LocalScreenNavHost = staticCompositionLocalOf { error("LocalScreenNavHost not ready") @@ -31,10 +32,6 @@ fun ScreenNavHost( navHostController: NavHostController = rememberNavController(), startDestination: String = AUTHENTICATION_ROUTE, ) { - val lexiconListState = rememberLazyListState() - val questListState = rememberLazyListState() - val locationListState = rememberLazyListState() - CompositionLocalProvider( LocalScreenNavHost provides navHostController, ) { @@ -44,22 +41,19 @@ fun ScreenNavHost( ) { composableAuthentication( onSignIn = { - navHostController.navigateToHome(option = rootOption()) + navHostController.navigateToLanding(option = rootOption()) }, ) - composableHome( - lexiconListState = lexiconListState, - questListState = questListState, - locationListState = locationListState, - ) composableLanding() - composableLexiconDetail() composableSearch() + composableLexicon() + composableLexiconDetail() + composableQuests() composableQuestDetail() + composableLocations() composableLocationDetail() composableCharacterSheet() composableSpellDetail() - composableSummary() } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableHome.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableHome.kt deleted file mode 100644 index c329268..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableHome.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.pixelized.rplexicon.ui.navigation.screens - -import androidx.compose.foundation.lazy.LazyListState -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import androidx.navigation.NavOptionsBuilder -import com.pixelized.rplexicon.ui.navigation.HomeNavHost -import com.pixelized.rplexicon.ui.navigation.NavigationAnimation -import com.pixelized.rplexicon.ui.navigation.animatedComposable - -private const val ROUTE = "home" - -const val HOME_ROUTE = ROUTE - -fun NavGraphBuilder.composableHome( - lexiconListState: LazyListState, - questListState: LazyListState, - locationListState: LazyListState, -) { - animatedComposable( - route = HOME_ROUTE, - animation = NavigationAnimation.Push, - ) { - HomeNavHost( - lexiconListState = lexiconListState, - questListState = questListState, - locationListState = locationListState, - ) - } -} - -fun NavHostController.navigateToHome( - option: NavOptionsBuilder.() -> Unit = {}, -) { - navigate(route = ROUTE, builder = option) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLexicon.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexicon.kt similarity index 67% rename from app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLexicon.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexicon.kt index 14d054b..66c2c9c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLexicon.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexicon.kt @@ -1,6 +1,5 @@ -package com.pixelized.rplexicon.ui.navigation.pages +package com.pixelized.rplexicon.ui.navigation.screens -import androidx.compose.foundation.lazy.LazyListState import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptionsBuilder @@ -12,16 +11,12 @@ private const val ROUTE = "lexicon" const val LEXICON_LIST_ROUTE = ROUTE -fun NavGraphBuilder.composableLexicon( - lazyListState: LazyListState, -) { +fun NavGraphBuilder.composableLexicon() { animatedComposable( route = LEXICON_LIST_ROUTE, - animation = NavigationAnimation.Fade, + animation = NavigationAnimation.Push, ) { - LexiconScreen( - lazyListState = lazyListState - ) + LexiconScreen() } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLocation.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLocation.kt similarity index 66% rename from app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLocation.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLocation.kt index 4d660a2..7cd2131 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableLocation.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLocation.kt @@ -1,6 +1,5 @@ -package com.pixelized.rplexicon.ui.navigation.pages +package com.pixelized.rplexicon.ui.navigation.screens -import androidx.compose.foundation.lazy.LazyListState import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptionsBuilder @@ -12,16 +11,12 @@ private const val ROUTE = "locations" const val LOCATION_LIST_ROUTE = ROUTE -fun NavGraphBuilder.composableLocations( - lazyListState: LazyListState, -) { +fun NavGraphBuilder.composableLocations() { animatedComposable( route = LOCATION_LIST_ROUTE, - animation = NavigationAnimation.Fade, + animation = NavigationAnimation.Push, ) { - LocationScreen( - lazyListState = lazyListState, - ) + LocationScreen() } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableQuestList.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableQuestList.kt similarity index 66% rename from app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableQuestList.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableQuestList.kt index 39af5e7..8e1ad9d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/pages/ComposableQuestList.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableQuestList.kt @@ -1,6 +1,5 @@ -package com.pixelized.rplexicon.ui.navigation.pages +package com.pixelized.rplexicon.ui.navigation.screens -import androidx.compose.foundation.lazy.LazyListState import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptionsBuilder @@ -12,16 +11,12 @@ private const val ROUTE = "quests" const val QUEST_LIST_ROUTE = ROUTE -fun NavGraphBuilder.composableQuests( - lazyListState: LazyListState, -) { +fun NavGraphBuilder.composableQuests() { animatedComposable( route = QUEST_LIST_ROUTE, - animation = NavigationAnimation.Fade, + animation = NavigationAnimation.Push, ) { - QuestListScreen( - lazyListState = lazyListState, - ) + QuestListScreen() } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt index 85df76f..cd2a6a6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt @@ -114,7 +114,7 @@ fun AuthenticationScreen( onSignIn = onSignIn, onSignInError = { snack.showSnackbar( - message = it?.message ?: context.getString(R.string.error_generic) + message = it?.message ?: context.getString(R.string.error__generic) ) } ) @@ -172,7 +172,8 @@ private fun AuthenticationScreenContent( } Text( - style = remember { typography.labelSmall.copy(fontStyle = FontStyle.Italic) }, + style = typography.labelSmall, + fontStyle = FontStyle.Italic, text = version.toText(), ) } @@ -353,7 +354,7 @@ private fun rememberBackgroundGradient(): Brush { @Composable private fun rememeberGoogleStringResource(): AnnotatedString { val default = LocalTextStyle.current.toSpanStyle() - val google = stringResource(id = R.string.action_google_sign_in) + val google = stringResource(id = R.string.authentication__google_sign_in_action) return remember { buildAnnotatedString { withStyle( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/GenericHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/GenericHeader.kt index 28f4256..f621540 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/GenericHeader.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/GenericHeader.kt @@ -60,7 +60,7 @@ fun GenericHeader( maxLines = 1, text = annotateWithDropCap( text = stringResource(id = label), - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ), ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt index f66fdac..0c9b414 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt @@ -83,7 +83,7 @@ fun SpellHeader( }, header.level ), - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ), ) header.count?.let { count -> diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/DeathHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/DeathHeader.kt index 7e1666e..a092d56 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/DeathHeader.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/DeathHeader.kt @@ -74,7 +74,7 @@ fun DeathHeader( .padding(horizontal = 8.dp), style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Light, - text = stringResource(id = R.string.generic_success), + text = stringResource(id = R.string.character_sheet__death_header__success_label), ) } Icon( @@ -105,7 +105,7 @@ fun DeathHeader( .padding(horizontal = 8.dp), style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Light, - text = stringResource(id = R.string.generic_failure), + text = stringResource(id = R.string.character_sheet__death_header__failure_label), ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SkillDetail.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SkillDetail.kt index ae97ac5..748139d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SkillDetail.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SkillDetail.kt @@ -83,7 +83,7 @@ fun SkillDetail( text = detail.name, spanStyles = listOf( AnnotatedString.Range( - item = MaterialTheme.lexicon.typography.titleMediumDropCap, + item = MaterialTheme.lexicon.typography.dropCap.titleMedium, start = 0, end = Integer.min(1, detail.name.length), ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationDetail.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationDetail.kt index 0b753e8..31d6013 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationDetail.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationDetail.kt @@ -82,7 +82,7 @@ fun AlterationDetail( maxLines = 1, text = annotateWithDropCap( text = detail.name, - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ), ) detail.original?.let { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationViewModel.kt index 9c500e7..b0967c8 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/alteration/AlterationViewModel.kt @@ -90,7 +90,7 @@ class AlterationViewModel @Inject constructor( source = alteration.source, target = alteration.target, description = description?.description - ?: context.getString(R.string.no_available_description) + ?: context.getString(R.string.default_missing_description) ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt index 29d7c4d..c190872 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt @@ -6,14 +6,12 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -27,10 +25,13 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider @@ -51,85 +52,65 @@ data class LandingItemUio( fun LandingItem( modifier: Modifier = Modifier, item: LandingItemUio, + paddings: PaddingValues = PaddingValues(), + imagePadding: PaddingValues = PaddingValues(all = 16.dp), + shape: Shape = remember { RoundedCornerShape(size = 8.dp) }, rotation: Float = 0f, - ratio: Float = 0.23f, alpha: Float = 0.6f, - padding: PaddingValues = PaddingValues(), - onClick: (LandingItemUio) -> Unit, -) { - LandingHorizontalItem( - modifier = modifier, - item = item, - rotation = rotation, - ratio = ratio, - alpha = alpha, - padding = padding, - onClick = onClick, - ) -} - -@Composable -private fun LandingHorizontalItem( - modifier: Modifier = Modifier, - item: LandingItemUio, - rotation: Float, - ratio: Float, - alpha: Float, - padding: PaddingValues, - onClick: (LandingItemUio) -> Unit, + onClick: () -> Unit, ) { Box( - modifier = modifier.clickable { onClick(item) }, + modifier = Modifier + .clip(shape = shape) + .clickable(onClick = onClick) + .padding(paddingValues = paddings) + .then(other = modifier), + contentAlignment = Alignment.BottomCenter ) { + Image( + modifier = Modifier + .matchParentSize() + .padding(paddingValues = imagePadding) + .rotate(degrees = rotation), + painter = painterResource(id = item.icon), + alpha = alpha, + contentScale = ContentScale.FillHeight, + alignment = Alignment.TopCenter, + colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), + contentDescription = null, + ) + Box( modifier = Modifier - .fillMaxWidth() - .height(height = 48.dp) - .clip(shape = RectangleShape), - ) { - Image( - modifier = Modifier - .padding(paddingValues = padding) - .fillMaxWidth(fraction = ratio) - .aspectRatio(1f) - .rotate(degrees = rotation) - .align(alignment = Alignment.TopEnd), - painter = painterResource(id = item.icon), - alpha = alpha, - contentScale = ContentScale.FillBounds, - colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), - contentDescription = null, - ) - Box( - modifier = Modifier - .matchParentSize() - .background(brush = rememberBackgroundGradient()) - ) - } - Row( - modifier = Modifier - .padding(paddingValues = padding) - .align(alignment = Alignment.CenterStart), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + .matchParentSize() + .background(brush = rememberBackgroundGradient()) + ) + + Column( + modifier = Modifier.align(alignment = Alignment.BottomCenter), + horizontalAlignment = Alignment.CenterHorizontally, ) { item.title?.let { Text( - modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Normal, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, text = annotateWithDropCap( text = it, - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ) ) } item.subTitle?.let { Text( - modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Light, fontStyle = FontStyle.Italic, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, text = it, ) } @@ -141,7 +122,7 @@ private fun LandingHorizontalItem( private fun rememberBackgroundGradient(): Brush { val colorScheme = MaterialTheme.colorScheme return remember { - Brush.horizontalGradient( + Brush.verticalGradient( colors = listOf( colorScheme.surface.copy(alpha = 0.5f), colorScheme.surface.copy(alpha = 1.0f), @@ -159,9 +140,8 @@ private fun LandingItemPreview( LexiconTheme { Surface { LandingItem( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.size(256.dp), item = preview, - padding = PaddingValues(horizontal = 16.dp), onClick = { }, ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt index 39920a6..b40addd 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt @@ -2,114 +2,304 @@ package com.pixelized.rplexicon.ui.screens.landing import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.BackgroundImage +import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost +import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet +import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexicon +import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocation +import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestList +import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch +import com.pixelized.rplexicon.ui.navigation.screens.navigateToSummary import com.pixelized.rplexicon.ui.theme.LexiconTheme +import com.pixelized.rplexicon.utilitary.annotateMajWithDropCap +import com.pixelized.rplexicon.utilitary.extentions.lexicon +import com.pixelized.rplexicon.utilitary.extentions.scrollOffset +import com.pixelized.rplexicon.utilitary.extentions.uri @Composable fun LandingScreen() { + val screen = LocalScreenNavHost.current + Surface( + modifier = Modifier.fillMaxSize() + ) { + LandingContent( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding(), + padding = PaddingValues( + start = 16.dp, + top = MaterialTheme.lexicon.dimens.detailPadding, + end = 16.dp, + bottom = 16.dp, + ), + onCharacter = { screen.navigateToCharacterSheet(name = it) }, + onSearch = { + screen.navigateToSearch( + enableLexicon = true, + enableQuests = true, + enableLocations = true, + enableSpells = true, + ) + }, + onGameMaster = { screen.navigateToSummary() }, + onLexicon = { screen.navigateToLexicon() }, + onQuest = { screen.navigateToQuestList() }, + onMap = { screen.navigateToLocation() }, + ) + } } @Composable private fun LandingContent( modifier: Modifier = Modifier, - padding: Dp = 16.dp, - items: State>, + padding: PaddingValues = PaddingValues(16.dp), + scrollState: ScrollState = rememberScrollState(), + sectionPadding: Dp = 32.dp, + onCharacter: (String) -> Unit, + onSearch: () -> Unit, + onGameMaster: () -> Unit, + onLexicon: () -> Unit, + onQuest: () -> Unit, + onMap: () -> Unit, ) { - LazyColumn( - modifier = modifier, - contentPadding = PaddingValues(vertical = padding), - verticalArrangement = Arrangement.spacedBy(space = 8.dp), - content = { - items(count = 1) { - Text( - modifier = Modifier.padding(horizontal = padding), - style = MaterialTheme.typography.labelSmall, - fontStyle = FontStyle.Italic, - fontWeight = FontWeight.Light, - text = "Feuilles de personnages", - ) + Box( + modifier = modifier.verticalScroll(scrollState), + ) { + BackgroundImage( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .scrollOffset(scrollState) { it / 2 }, + contentScale = ContentScale.Crop, + model = R.drawable.im_naderius.uri, + ) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues = padding), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.displaySmall, + textAlign = TextAlign.Center, + text = annotateMajWithDropCap( + text = stringResource(id = R.string.landing__title), + style = MaterialTheme.lexicon.typography.dropCap.displaySmall, + ), + ) + + Text( + modifier = Modifier.padding(top = sectionPadding), + style = MaterialTheme.typography.labelSmall, + fontStyle = FontStyle.Italic, + fontWeight = FontWeight.Light, + text = stringResource(id = R.string.landing__caterogy__character), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + stringResource(id = R.string.landing__character_brulkhai).let { character -> + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 8.dp), + item = LandingItemUio( + icon = R.drawable.ic_class_barbarian_24, + title = character, + subTitle = stringResource(id = R.string.class_barbarian), + ), + onClick = { onCharacter(character) }, + ) + } + stringResource(id = R.string.landing__character_leandre).let { character -> + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 8.dp), + item = LandingItemUio( + icon = R.drawable.ic_class_cleric_24, + title = character, + subTitle = stringResource(id = R.string.class_cleric), + ), + onClick = { onCharacter(character) }, + ) + } + stringResource(id = R.string.landing__character_nelia).let { character -> + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 8.dp), + item = LandingItemUio( + icon = R.drawable.ic_class_ranger_24, + title = character, + subTitle = stringResource(id = R.string.class_ranger), + ), + onClick = { onCharacter(character) }, + ) + } } - items(items = items.value) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + stringResource(id = R.string.landing__character_tigrane).let { character -> + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 8.dp), + item = LandingItemUio( + icon = R.drawable.ic_class_warlock_24, + title = character, + subTitle = stringResource(id = R.string.class_warlock), + ), + onClick = { onCharacter(character) }, + ) + } + stringResource(id = R.string.landing__character_unathana).let { character -> + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 8.dp), + item = LandingItemUio( + icon = R.drawable.ic_class_bard_24, + title = character, + subTitle = stringResource(id = R.string.class_bard), + ), + onClick = { onCharacter(character) }, + ) + } + Spacer(modifier = Modifier.weight(1f)) + } + + Text( + modifier = Modifier.padding(top = sectionPadding), + style = MaterialTheme.typography.labelSmall, + fontStyle = FontStyle.Italic, + fontWeight = FontWeight.Light, + text = stringResource(id = R.string.landing__caterogy__tools), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { LandingItem( - modifier = Modifier.fillMaxWidth(), - item = it, - rotation = 270f, - ratio = 0.75f, - alpha = 0.5f, - padding = PaddingValues(horizontal = padding), - onClick = { }, - ) - } - items(count = 1) { - Text( modifier = Modifier - .padding(horizontal = padding) - .padding(top = padding), - style = MaterialTheme.typography.labelSmall, - fontStyle = FontStyle.Italic, - fontWeight = FontWeight.Light, - text = "Encyclopédie", - ) - } - items(count = 1) { - LandingItem( - modifier = Modifier.fillMaxWidth(), + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 24.dp), item = LandingItemUio( - title = "Lexique de personnages", + title = stringResource(id = R.string.summary__title), + subTitle = null, + icon = R.drawable.ic_crowned_skull_24, + ), + onClick = onGameMaster, + ) + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 24.dp), + item = LandingItemUio( + title = stringResource(id = R.string.default_search_label), + subTitle = null, + icon = R.drawable.ic_baseline_search_24, + ), + onClick = onSearch, + ) + Spacer(modifier = Modifier.weight(1f)) + } + + Text( + modifier = Modifier.padding(top = sectionPadding), + style = MaterialTheme.typography.labelSmall, + fontStyle = FontStyle.Italic, + fontWeight = FontWeight.Light, + text = stringResource(id = R.string.landing__caterogy__encyclopedia), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + LandingItem( + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 24.dp), + item = LandingItemUio( + title = stringResource(id = R.string.lexicon_list__title), subTitle = null, icon = R.drawable.ic_visored_helm_24, ), - padding = PaddingValues(horizontal = padding), - onClick = { }, + onClick = onLexicon, ) - } - items(count = 1) { LandingItem( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 24.dp), item = LandingItemUio( - title = "Journal de quêtes", + title = stringResource(id = R.string.quest_list__title), subTitle = null, icon = R.drawable.ic_scroll_unfurled_24, ), - padding = PaddingValues(horizontal = padding), - onClick = { }, + onClick = onQuest, ) - } - items(count = 1) { LandingItem( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .height(128.dp) + .weight(1f), + imagePadding = PaddingValues(all = 24.dp), item = LandingItemUio( - title = "Cartes du monde", + title = stringResource(id = R.string.location_list__title), subTitle = null, icon = R.drawable.ic_treasure_map_24, ), - padding = PaddingValues(horizontal = padding), - onClick = { }, + onClick = onMap, ) } - }, - ) + } + } } @Composable @@ -120,7 +310,12 @@ private fun LandingPreview() { Surface { LandingContent( modifier = Modifier.fillMaxSize(), - items = remember { mutableStateOf(landingItems()) } + onCharacter = { }, + onSearch = { }, + onGameMaster = { }, + onLexicon = { }, + onQuest = { }, + onMap = { }, ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt index e2411e1..f44bec3 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt @@ -152,7 +152,7 @@ private fun LexiconDetailContent( } }, title = { - Text(text = stringResource(id = R.string.detail_title)) + Text(text = stringResource(id = R.string.lexicon_detail__title)) }, ) }, @@ -191,7 +191,7 @@ private fun LexiconDetailContent( style = typography.base.headlineSmall, text = annotate( text = it, - dropCapRegex styleWith typography.headlineSmallDropCap, + dropCapRegex styleWith typography.dropCap.headlineSmall, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -248,7 +248,7 @@ private fun LexiconDetailContent( modifier = Modifier.alignByBaseline(), style = typography.base.bodyMedium, fontWeight = FontWeight.Bold, - text = stringResource(id = R.string.detail_status), + text = stringResource(id = R.string.lexicon_detail__status), ) Text( modifier = Modifier.alignByBaseline(), @@ -270,7 +270,7 @@ private fun LexiconDetailContent( modifier = Modifier.alignByBaseline(), style = typography.base.bodyMedium, fontWeight = FontWeight.Bold, - text = stringResource(id = R.string.detail_location), + text = stringResource(id = R.string.lexicon_detail__location), ) Text( modifier = Modifier.alignByBaseline(), @@ -287,14 +287,14 @@ private fun LexiconDetailContent( Text( modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp), style = typography.base.titleMedium, - text = stringResource(id = R.string.detail_description), + text = stringResource(id = R.string.lexicon_detail__description), ) Text( modifier = Modifier.padding(horizontal = 16.dp), style = typography.base.bodyMedium, text = annotate( text = it, - dropCapRegex styleWith typography.bodyMediumDropCap, + dropCapRegex styleWith typography.dropCap.bodyMedium, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -304,14 +304,14 @@ private fun LexiconDetailContent( Text( modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp), style = typography.base.titleMedium, - text = stringResource(id = R.string.detail_history), + text = stringResource(id = R.string.lexicon_detail__history), ) Text( modifier = Modifier.padding(horizontal = 16.dp), style = typography.base.bodyMedium, text = annotate( text = it, - dropCapRegex styleWith typography.bodyMediumDropCap, + dropCapRegex styleWith typography.dropCap.bodyMedium, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -322,7 +322,7 @@ private fun LexiconDetailContent( Text( modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp), style = typography.base.titleMedium, - text = stringResource(id = R.string.detail_portrait), + text = stringResource(id = R.string.lexicon_detail__portrait), ) LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt index 6bb53a0..46f0ec8 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -40,6 +39,8 @@ import androidx.compose.ui.unit.dp import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.LOS_FULL +import com.pixelized.rplexicon.utilitary.annotateMajWithDropCap +import com.pixelized.rplexicon.utilitary.annotateWithDropCap import com.pixelized.rplexicon.utilitary.extentions.lexicon import com.pixelized.rplexicon.utilitary.extentions.placeholder @@ -127,18 +128,9 @@ fun LexiconItem( fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, - text = AnnotatedString( + text = annotateWithDropCap( text = item.name, - spanStyles = when (item.placeholder) { - true -> emptyList() - else -> listOf( - AnnotatedString.Range( - item = typography.titleMediumDropCap, - start = 0, - end = 1, - ) - ) - }, + style = typography.dropCap.titleMedium, ), ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt index 2011766..19ae3f6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items @@ -17,10 +18,14 @@ import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -29,6 +34,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -39,6 +46,7 @@ import com.pixelized.rplexicon.ui.composable.CategoryHeader 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.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail @@ -53,10 +61,10 @@ import kotlinx.coroutines.launch @Composable fun LexiconScreen( viewModel: LexiconViewModel = hiltViewModel(), - lazyListState: LazyListState, ) { val screen = LocalScreenNavHost.current val scope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() val refresh = rememberPullRefreshState( refreshing = false, onRefresh = { @@ -75,11 +83,17 @@ fun LexiconScreen( modifier = Modifier.fillMaxSize(), ) { LexiconScreenContent( + modifier = Modifier + .fillMaxSize() + .systemBarsPadding(), items = viewModel.items, - lazyColumnState = lazyListState, + lazyListState = lazyListState, refreshState = refresh, refreshing = viewModel.isLoading, isFabExpended = isFabExpended, + onBack = { + screen.popBackStack() + }, onSearch = { screen.navigateToSearch(enableLexicon = true) }, @@ -97,87 +111,110 @@ fun LexiconScreen( } } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable private fun LexiconScreenContent( modifier: Modifier = Modifier, - lazyColumnState: LazyListState, + lazyListState: LazyListState, refreshState: PullRefreshState, refreshing: State, items: State>, isFabExpended: State, + onBack: () -> Unit, onSearch: () -> Unit, onItem: (LexiconItemUio) -> Unit, onCharacterSheet: (LexiconItemUio) -> Unit, ) { - Box( + Scaffold( modifier = modifier, - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .pullRefresh(state = refreshState), - state = lazyColumnState, - contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, + containerColor = Color.Transparent, + topBar = { + val shadow = rememberAnimatedShadow(lazyListState) + TopAppBar( + modifier = Modifier.shadow(elevation = shadow.value), + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), + contentDescription = null + ) + } + }, + title = { + Text(text = stringResource(id = R.string.lexicon_list__title)) + }, + ) + }, + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues), ) { - items.value.forEachIndexed { index, entry -> - entry.category?.let { - item( - contentType = { "Header" }, + LazyColumn( + modifier = Modifier + .fillMaxSize() + .pullRefresh(state = refreshState), + state = lazyListState, + contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, + ) { + items.value.forEachIndexed { index, entry -> + entry.category?.let { + item( + contentType = { "Header" }, + ) { + CategoryHeader( + modifier = Modifier + .padding(top = if (index == 0) 0.dp else 16.dp) + .padding(horizontal = 16.dp), + text = it, + ) + } + } + items( + items = entry.items, + key = { it.id }, + contentType = { "Lexicon" }, ) { - CategoryHeader( - modifier = Modifier - .padding(top = if (index == 0) 0.dp else 16.dp) - .padding(horizontal = 16.dp), - text = it, + LexiconItem( + item = it, + onItem = onItem, + onCharacterSheet = onCharacterSheet ) } } - items( - items = entry.items, - key = { it.id }, - contentType = { "Lexicon" }, - ) { - LexiconItem( - item = it, - onItem = onItem, - onCharacterSheet = onCharacterSheet - ) - } } - } - AnimatedVisibility( - modifier = Modifier - .padding(all = 16.dp) - .align(Alignment.BottomEnd), - visible = items.value.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - FloatingActionButton( - expended = isFabExpended.value, - onClick = onSearch, - colors = ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - icon = { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_search_24), - contentDescription = null, - ) - }, - text = { - Text(text = stringResource(id = R.string.lexicon_search)) - }, + AnimatedVisibility( + modifier = Modifier + .padding(all = 16.dp) + .align(Alignment.BottomEnd), + visible = items.value.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + FloatingActionButton( + expended = isFabExpended.value, + onClick = onSearch, + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + icon = { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_search_24), + contentDescription = null, + ) + }, + text = { + Text(text = stringResource(id = R.string.default_search_label)) + }, + ) + } + + Loader( + modifier = Modifier.align(Alignment.TopCenter), + refreshState = refreshState, + refreshing = refreshing, ) } - - Loader( - modifier = Modifier.align(Alignment.TopCenter), - refreshState = refreshState, - refreshing = refreshing, - ) } } @@ -190,7 +227,7 @@ private fun LexiconScreenContentPreview() { Surface { LexiconScreenContent( modifier = Modifier.fillMaxSize(), - lazyColumnState = rememberLazyListState(), + lazyListState = rememberLazyListState(), refreshState = rememberPullRefreshState( refreshing = false, onRefresh = {}, @@ -198,6 +235,7 @@ private fun LexiconScreenContentPreview() { refreshing = remember { mutableStateOf(false) }, isFabExpended = remember { mutableStateOf(true) }, items = rememberLexiconGroupStatePreview(), + onBack = { }, onSearch = { }, onItem = { }, onCharacterSheet = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt index 53637ac..e360583 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt @@ -53,7 +53,7 @@ class LexiconViewModel @Inject constructor( .groupBy( keySelector = { it.category - ?: context.getString(R.string.default_category_other) + ?: context.getString(R.string.default_category_other_label) }, valueTransform = { item -> LexiconItemUio( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt index 53163a0..ebae578 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt @@ -154,7 +154,7 @@ fun LocationDetailScreen( ) if (result == SnackbarResult.ActionPerformed) { viewModel.clip( - label = context.getString(R.string.map_label), + label = context.getString(R.string.location_detail__coordinates_toast), coordinate = it, ) uriHandler.openUri( @@ -270,7 +270,7 @@ private fun LocationContent( } }, title = { - Text(text = stringResource(id = R.string.map_title)) + Text(text = stringResource(id = R.string.location_detail__title)) }, ) }, @@ -343,7 +343,7 @@ private fun LocationContent( textAlign = TextAlign.Center, text = annotateWithDropCap( text = marquee.value?.name ?: "", - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ) ) } @@ -426,7 +426,7 @@ private fun LocationContent( style = MaterialTheme.typography.headlineSmall, text = annotate( text = item.value?.name ?: "", - dropCapRegex styleWith typography.headlineSmallDropCap, + dropCapRegex styleWith typography.dropCap.headlineSmall, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -438,7 +438,7 @@ private fun LocationContent( style = MaterialTheme.typography.bodyMedium, text = annotate( text = it, - dropCapRegex styleWith typography.bodyMediumDropCap, + dropCapRegex styleWith typography.dropCap.bodyMedium, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -451,8 +451,8 @@ private fun LocationContent( .padding(horizontal = 16.dp), style = MaterialTheme.typography.titleMedium, text = annotateWithDropCap( - text = stringResource(id = R.string.map_destination), - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + text = stringResource(id = R.string.location_detail__destination), + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ) ) item.value?.marquees?.forEach { @@ -474,7 +474,7 @@ private fun LocationContent( style = MaterialTheme.typography.bodyMedium, text = annotate( text = it.name, - dropCapRegex styleWith typography.bodyMediumDropCap, + dropCapRegex styleWith typography.dropCap.bodyMedium, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -491,8 +491,8 @@ private fun LocationContent( .padding(horizontal = 16.dp), style = MaterialTheme.typography.titleMedium, text = annotateWithDropCap( - text = stringResource(id = R.string.map_illustrations), - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + text = stringResource(id = R.string.location_detail__illustrations), + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ) ) LazyRow( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/MarqueeItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/MarqueeItem.kt index 2663279..5a29e54 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/MarqueeItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/MarqueeItem.kt @@ -39,7 +39,7 @@ fun MarqueeItem( maxLines = 3, text = annotateWithDropCap( text = marquee.name, - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ), ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationItem.kt index 5216f4e..5e421af 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationItem.kt @@ -112,7 +112,7 @@ fun LocationItem( true -> emptyList() else -> listOf( AnnotatedString.Range( - item = typography.titleMediumDropCap, + item = typography.dropCap.titleMedium, start = 0, end = 1, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt index 4f0ce1b..82fc943 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt @@ -16,10 +16,14 @@ import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -28,6 +32,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -38,6 +44,7 @@ import com.pixelized.rplexicon.ui.composable.CategoryHeader 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.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocationDetail import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch @@ -51,10 +58,10 @@ import kotlinx.coroutines.launch @Composable fun LocationScreen( viewModel: LocationViewModel = hiltViewModel(), - lazyListState: LazyListState, ) { val screen = LocalScreenNavHost.current val scope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() val refresh = rememberPullRefreshState( refreshing = false, @@ -75,10 +82,13 @@ fun LocationScreen( ) { LocationContent( items = viewModel.items, - lazyColumnState = lazyListState, + lazyListState = lazyListState, refreshState = refresh, refreshing = viewModel.isLoading, isFabExpended = isFabExpended, + onBack = { + screen.popBackStack() + }, onSearch = { screen.navigateToSearch(enableLocations = true) }, @@ -93,85 +103,108 @@ fun LocationScreen( } } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable private fun LocationContent( modifier: Modifier = Modifier, - lazyColumnState: LazyListState, + lazyListState: LazyListState, refreshState: PullRefreshState, refreshing: State, items: State>, isFabExpended: State, + onBack: () -> Unit, onSearch: () -> Unit, onItem: (LocationItemUio) -> Unit, ) { - Box( + Scaffold( modifier = modifier, - contentAlignment = Alignment.TopCenter, - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .pullRefresh(state = refreshState), - state = lazyColumnState, - contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, + containerColor = Color.Transparent, + topBar = { + val shadow = rememberAnimatedShadow(lazyListState) + TopAppBar( + modifier = Modifier.shadow(elevation = shadow.value), + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), + contentDescription = null + ) + } + }, + title = { + Text(text = stringResource(id = R.string.location_list__title)) + }, + ) + }, + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues), + contentAlignment = Alignment.TopCenter, ) { - items.value.forEachIndexed { index, entry -> - entry.category?.let { - item( - contentType = { "Header" }, + LazyColumn( + modifier = Modifier + .fillMaxSize() + .pullRefresh(state = refreshState), + state = lazyListState, + contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, + ) { + items.value.forEachIndexed { index, entry -> + entry.category?.let { + item( + contentType = { "Header" }, + ) { + CategoryHeader( + modifier = Modifier + .padding(top = if (index == 0) 0.dp else 16.dp) + .padding(horizontal = 16.dp), + text = it, + ) + } + } + items( + items = entry.maps, + key = { it.id }, + contentType = { "Location" }, ) { - CategoryHeader( - modifier = Modifier - .padding(top = if (index == 0) 0.dp else 16.dp) - .padding(horizontal = 16.dp), - text = it, + LocationItem( + item = it, + onItem = onItem, ) } } - items( - items = entry.maps, - key = { it.id }, - contentType = { "Location" }, - ) { - LocationItem( - item = it, - onItem = onItem, - ) - } } - } - AnimatedVisibility( - modifier = Modifier - .padding(all = 16.dp) - .align(Alignment.BottomEnd), - visible = items.value.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - FloatingActionButton( - expended = isFabExpended.value, - onClick = onSearch, - colors = ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - icon = { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_search_24), - contentDescription = null, - ) - }, - text = { - Text(text = stringResource(id = R.string.lexicon_search)) - }, + AnimatedVisibility( + modifier = Modifier + .padding(all = 16.dp) + .align(Alignment.BottomEnd), + visible = items.value.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + FloatingActionButton( + expended = isFabExpended.value, + onClick = onSearch, + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + icon = { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_search_24), + contentDescription = null, + ) + }, + text = { + Text(text = stringResource(id = R.string.default_search_label)) + }, + ) + } + + Loader( + refreshState = refreshState, + refreshing = refreshing, ) } - - Loader( - refreshState = refreshState, - refreshing = refreshing, - ) } } @@ -184,7 +217,7 @@ private fun QuestListPreview() { Surface { LocationContent( modifier = Modifier.fillMaxSize(), - lazyColumnState = rememberLazyListState(), + lazyListState = rememberLazyListState(), refreshState = rememberPullRefreshState( refreshing = false, onRefresh = { }, @@ -212,6 +245,7 @@ private fun QuestListPreview() { ) }, isFabExpended = remember { mutableStateOf(true) }, + onBack = { }, onSearch = { }, onItem = { }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationViewModel.kt index 6c9b3cf..45dd9ad 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationViewModel.kt @@ -51,7 +51,7 @@ class LocationViewModel @Inject constructor( .groupBy( keySelector = { entry -> entry.category - ?: context.getString(R.string.default_category_other) + ?: context.getString(R.string.default_category_other_label) }, valueTransform = { entry -> LocationItemUio( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt index 8051e25..134ac20 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt @@ -150,7 +150,7 @@ private fun QuestDetailContent( } }, title = { - Text(text = stringResource(id = R.string.quest_detail_title)) + Text(text = stringResource(id = R.string.quest_detail__title)) }, ) }, @@ -160,8 +160,8 @@ private fun QuestDetailContent( ) { Box( modifier = Modifier - .fillMaxWidth() .aspectRatio(ratio = 1f) + .fillMaxWidth() .scrollOffset(scrollState = state) { -it / 2 }, ) { BackgroundImage( @@ -176,7 +176,7 @@ private fun QuestDetailContent( .rotate(degrees = 12f), style = MaterialTheme.lexicon.typography.stamp, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f), - text = stringResource(id = R.string.quest_detail_completed), + text = stringResource(id = R.string.quest_detail__completed), ) } } @@ -203,7 +203,7 @@ private fun QuestDetailContent( style = typography.base.headlineLarge, text = annotate( text = quest?.title ?: "", - dropCapRegex styleWith typography.headlineLargeDropCap, + dropCapRegex styleWith typography.dropCap.headlineLarge, highlightRegex styleWith typography.detail.highlightStyle, ), ) @@ -251,14 +251,14 @@ private fun QuestStep( maxLines = 3, text = annotate( text = it, - dropCapRegex styleWith typography.titleLargeDropCap, + dropCapRegex styleWith typography.dropCap.titleLarge, highlightRegex styleWith typography.detail.highlightStyle, ), ) } quest.giver?.let { SubTitle( - title = stringResource(id = R.string.quest_detail_giver), + title = stringResource(id = R.string.quest_detail__giver), label = annotate( text = when (quest.giverId) { null -> "$LOS_HOLLOW $it" @@ -271,7 +271,7 @@ private fun QuestStep( } quest.place?.let { SubTitle( - title = stringResource(id = R.string.quest_detail_area), + title = stringResource(id = R.string.quest_detail__area), label = annotate( text = when (quest.placeId) { null -> "$LOS_HOLLOW $it" @@ -284,7 +284,7 @@ private fun QuestStep( } quest.globalReward?.let { SubTitle( - title = stringResource(id = R.string.quest_detail_group_reward), + title = stringResource(id = R.string.quest_detail__group_reward), label = annotate( text = "$LOS_HOLLOW $it", highlightRegex styleWith typography.detail.highlightStyle, @@ -293,7 +293,7 @@ private fun QuestStep( } quest.individualReward?.let { SubTitle( - title = stringResource(id = R.string.quest_detail_individual_reward), + title = stringResource(id = R.string.quest_detail__individual_reward), label = annotate( text = "$LOS_HOLLOW $it", highlightRegex styleWith typography.detail.highlightStyle, @@ -305,7 +305,7 @@ private fun QuestStep( style = MaterialTheme.typography.bodyMedium, text = annotate( text = quest.description, - dropCapRegex styleWith typography.bodyMediumDropCap, + dropCapRegex styleWith typography.dropCap.bodyMedium, highlightRegex styleWith typography.detail.highlightStyle, ), ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestItem.kt index 0731b45..9f0cc55 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestItem.kt @@ -115,7 +115,7 @@ fun QuestItem( true -> emptyList() else -> listOf( AnnotatedString.Range( - item = typography.titleMediumDropCap, + item = typography.dropCap.titleMedium, start = 0, end = 1, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt index 1036b66..c65247c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt @@ -16,10 +16,14 @@ import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -28,6 +32,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -38,6 +44,7 @@ import com.pixelized.rplexicon.ui.composable.CategoryHeader 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.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestDetail import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch @@ -51,10 +58,10 @@ import kotlinx.coroutines.launch @Composable fun QuestListScreen( viewModel: QuestListViewModel = hiltViewModel(), - lazyListState: LazyListState, ) { - val scope = rememberCoroutineScope() val screen = LocalScreenNavHost.current + val scope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() val refresh = rememberPullRefreshState( refreshing = false, @@ -75,10 +82,13 @@ fun QuestListScreen( ) { QuestListContent( items = viewModel.items, - lazyColumnState = lazyListState, + lazyListState = lazyListState, refreshState = refresh, refreshing = viewModel.isLoading, isFabExpended = isFabExpended, + onBack = { + screen.popBackStack() + }, onSearch = { screen.navigateToSearch(enableQuests = true) }, @@ -93,83 +103,106 @@ fun QuestListScreen( } } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable private fun QuestListContent( modifier: Modifier = Modifier, - lazyColumnState: LazyListState, + lazyListState: LazyListState, refreshState: PullRefreshState, refreshing: State, isFabExpended: State, + onBack: () -> Unit, onSearch: () -> Unit, items: State>, onItem: (QuestItemUio) -> Unit, ) { - Box( + Scaffold( modifier = modifier, - contentAlignment = Alignment.TopCenter, - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .pullRefresh(state = refreshState), - state = lazyColumnState, - contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, - ) { - items.value.forEachIndexed { index, entry -> - item( - contentType = { "Header" }, - ) { - CategoryHeader( - modifier = Modifier - .padding(top = if (index == 0) 0.dp else 16.dp) - .padding(horizontal = 16.dp), - text = entry.category, - ) - } - items( - items = entry.quests, - key = { it.id }, - contentType = { "Quest" }, - ) { - QuestItem( - item = it, - onItem = onItem, - ) - } - } - } - - AnimatedVisibility( - modifier = Modifier - .padding(all = 16.dp) - .align(Alignment.BottomEnd), - visible = items.value.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - FloatingActionButton( - expended = isFabExpended.value, - onClick = onSearch, - colors = ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - icon = { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_search_24), - contentDescription = null, - ) + containerColor = Color.Transparent, + topBar = { + val shadow = rememberAnimatedShadow(lazyListState) + TopAppBar( + modifier = Modifier.shadow(elevation = shadow.value), + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), + contentDescription = null + ) + } }, - text = { - Text(text = stringResource(id = R.string.lexicon_search)) + title = { + Text(text = stringResource(id = R.string.quest_list__title)) }, ) - } + }, + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues), + contentAlignment = Alignment.TopCenter, + ) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .pullRefresh(state = refreshState), + state = lazyListState, + contentPadding = MaterialTheme.lexicon.dimens.itemListPadding, + ) { + items.value.forEachIndexed { index, entry -> + item( + contentType = { "Header" }, + ) { + CategoryHeader( + modifier = Modifier + .padding(top = if (index == 0) 0.dp else 16.dp) + .padding(horizontal = 16.dp), + text = entry.category, + ) + } + items( + items = entry.quests, + key = { it.id }, + contentType = { "Quest" }, + ) { + QuestItem( + item = it, + onItem = onItem, + ) + } + } + } - Loader( - refreshState = refreshState, - refreshing = refreshing, - ) + AnimatedVisibility( + modifier = Modifier + .padding(all = 16.dp) + .align(Alignment.BottomEnd), + visible = items.value.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + FloatingActionButton( + expended = isFabExpended.value, + onClick = onSearch, + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + icon = { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_search_24), + contentDescription = null, + ) + }, + text = { + Text(text = stringResource(id = R.string.default_search_label)) + }, + ) + } + + Loader( + refreshState = refreshState, + refreshing = refreshing, + ) + } } } @@ -182,7 +215,7 @@ private fun QuestListPreview() { Surface { QuestListContent( modifier = Modifier.fillMaxSize(), - lazyColumnState = rememberLazyListState(), + lazyListState = rememberLazyListState(), refreshState = rememberPullRefreshState( refreshing = false, onRefresh = {}, @@ -218,6 +251,7 @@ private fun QuestListPreview() { ) }, isFabExpended = remember { mutableStateOf(true) }, + onBack = { }, onSearch = { }, onItem = { }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt index 4817e60..1a6d855 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt @@ -52,7 +52,7 @@ class QuestListViewModel @Inject constructor( .groupBy( keySelector = { it.category - ?: context.getString(R.string.default_category_other) + ?: context.getString(R.string.default_category_other_label) }, valueTransform = { item -> QuestItemUio( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt index d8855bb..1fd3759 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt @@ -178,7 +178,7 @@ private fun RollOverlayContent( text = it, spanStyles = listOf( AnnotatedString.Range( - item = MaterialTheme.lexicon.typography.titleMediumDropCap, + item = MaterialTheme.lexicon.typography.dropCap.titleMedium, start = 0, end = Integer.min(1, it.length), ) @@ -220,7 +220,7 @@ private fun RollOverlayContent( ) { Text( modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(id = R.string.action_close), + text = stringResource(id = R.string.roll_overlay__close_action), ) } } @@ -257,6 +257,8 @@ private fun RollOverlayContent( .detailPaddingBottom(showDetail = showDetail, bottom = 128.dp), dice = dice, onDice = onDice, + criticalSuccess = stringResource(id = R.string.roll_overlay__critical_success), + criticalFailure = stringResource(id = R.string.roll_overlay__critical_failure), ) AnimatedVisibility( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt index 3c8a8b7..a077c46 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt @@ -136,7 +136,7 @@ class RollOverlayViewModel @Inject constructor( source = alteration.source, target = alteration.target, description = description?.description - ?: context.getString(R.string.no_available_description) + ?: context.getString(R.string.default_missing_description) ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt index 6c170c8..f827ada 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt @@ -36,9 +36,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign 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 androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @@ -72,6 +73,8 @@ fun RollDice( modifier: Modifier = Modifier, diceSize: Dp = 64.dp * 2.5f, dice: State, + criticalSuccess: String, + criticalFailure: String, onDice: () -> Unit, ) { Column( @@ -102,6 +105,8 @@ fun RollDice( Critical( modifier = Modifier.fillMaxWidth(), dice = dice, + criticalSuccess = criticalSuccess, + criticalFailure = criticalFailure, ) } } @@ -160,6 +165,8 @@ private fun Result( private fun Critical( modifier: Modifier = Modifier, dice: State, + criticalSuccess: String, + criticalFailure: String, ) { val state = remember { derivedStateOf { @@ -189,8 +196,8 @@ private fun Critical( else -> MaterialTheme.colorScheme.onSurface }, text = when (it) { - 1 -> stringResource(id = R.string.generic_success_critical) - 2 -> stringResource(id = R.string.generic_failure_critical) + 1 -> criticalSuccess + 2 -> criticalFailure else -> "" }, ) @@ -199,7 +206,9 @@ private fun Critical( @Composable @Preview -private fun SkillRollPreview() { +private fun SkillRollPreview( + @PreviewParameter(SkillRollPreviewProvider::class) preview: Int, +) { LexiconTheme { Surface { RollDice( @@ -208,13 +217,20 @@ private fun SkillRollPreview() { RollDiceUio( icon = R.drawable.ic_d20_24, rotation = 0f, - isCriticalSuccess = true, - result = "20", + isCriticalSuccess = preview == 20, + isCriticalFailure = preview == 1, + result = "$preview", ) ) }, + criticalSuccess = "CRITICAL SUCCESS", + criticalFailure = "CRITICAL FAILURE", onDice = { }, ) } } +} + +private class SkillRollPreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(1, 14, 20) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchFilter.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchFilter.kt index ce0cb6a..2877a2d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchFilter.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchFilter.kt @@ -31,21 +31,21 @@ sealed class SearchFilterUio( class Lexicon( selected: State = mutableStateOf(true), ) : SearchFilterUio( - label = R.string.home_lexicon, + label = R.string.lexicon_list__title, selected = selected, ) class Quest( selected: State = mutableStateOf(true), ) : SearchFilterUio( - label = R.string.home_quest_log, + label = R.string.quest_list__title, selected = selected, ) class Location( selected: State = mutableStateOf(true), ) : SearchFilterUio( - label = R.string.home_location, + label = R.string.location_list__title, selected = selected, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/item/LexiconSearchItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/item/LexiconSearchItem.kt index e947719..084c228 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/item/LexiconSearchItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/item/LexiconSearchItem.kt @@ -122,7 +122,7 @@ private fun LexiconSearchItemPreview() { category = AnnotatedString(text = "Lexique $PUC_FULL Personnage joueur"), name = annotateWithDropCap( text = "Brulkhai", - style = MaterialTheme.lexicon.typography.titleMediumDropCap, + style = MaterialTheme.lexicon.typography.dropCap.titleMedium, ), diminutive = AnnotatedString( text = "Bru" diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt index 17cbb76..6f69235 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt @@ -147,7 +147,7 @@ private fun SpellDetailContent( style = typography.base.headlineSmall, text = annotate( text = detail.name, - dropCapRegex styleWith typography.headlineSmallDropCap, + dropCapRegex styleWith typography.dropCap.headlineSmall, highlightRegex styleWith typography.detail.highlightStyle, ), ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/SummaryScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/SummaryScreen.kt index 188847b..85785d5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/SummaryScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/SummaryScreen.kt @@ -189,7 +189,7 @@ private fun SummaryContent( ) } Text( - text = stringResource(id = R.string.summary_title), + text = stringResource(id = R.string.summary__title), style = MaterialTheme.typography.titleLarge, ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/pages/statistic/StatisticViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/pages/statistic/StatisticViewModel.kt index 48421a8..d20d88f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/pages/statistic/StatisticViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/pages/statistic/StatisticViewModel.kt @@ -4,7 +4,6 @@ import android.app.Application import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.rplexicon.R import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository @@ -20,7 +19,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.lang.Appendable import javax.inject.Inject @HiltViewModel @@ -76,7 +74,7 @@ class StatisticViewModel @Inject constructor( source = alteration.source, target = alteration.target, description = description?.description - ?: context.getString(R.string.no_available_description) + ?: context.getString(R.string.default_missing_description) ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt index caf32c8..09f9c28 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt @@ -29,11 +29,7 @@ val stampFontFamily = FontFamily( data class LexiconTypography( val base: Typography, val stamp: TextStyle, - val bodyMediumDropCap: SpanStyle, // TODO DropCap class - val titleMediumDropCap: SpanStyle, // TODO DropCap class - val titleLargeDropCap: SpanStyle, // TODO DropCap class - val headlineSmallDropCap: SpanStyle, // TODO DropCap class - val headlineLargeDropCap: SpanStyle, // TODO DropCap class + val dropCap: DropCapTypography, val detail: Detail, val search: Search, ) { @@ -52,37 +48,113 @@ data class LexiconTypography( val extractHighlight: SpanStyle, val extractBold: SpanStyle, ) + + @Stable + data class DropCapTypography( + val displayLarge: SpanStyle, + val displayMedium: SpanStyle, + val displaySmall: SpanStyle, + val headlineLarge: SpanStyle, + val headlineMedium: SpanStyle, + val headlineSmall: SpanStyle, + val titleLarge: SpanStyle, + val titleMedium: SpanStyle, + val titleSmall: SpanStyle, + val bodyLarge: SpanStyle, + val bodyMedium: SpanStyle, + val bodySmall: SpanStyle, + val labelLarge: SpanStyle, + val labelMedium: SpanStyle, + val labelSmall: SpanStyle, + ) { + constructor(base: Typography) : this( + displayLarge = base.displayLarge.toDropCapSpan( + sizeRatio = 1.6f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.07f), + ), + displayMedium = base.displayMedium.toDropCapSpan( + sizeRatio = 1.6f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.08f), + ), + displaySmall = base.displaySmall.toDropCapSpan( + // never used + sizeRatio = 1.6f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.07f), + ), + headlineLarge = base.headlineLarge.toDropCapSpan( + sizeRatio = 1.7f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.06f), + ), + headlineMedium = base.headlineMedium.toDropCapSpan( + sizeRatio = 1.8f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.06f), + ), + headlineSmall = base.headlineSmall.toDropCapSpan( + sizeRatio = 1.9f, + antiLetterSpacing = 4.sp, + baselineShift = BaselineShift(-0.06f), + ), + titleLarge = base.titleLarge.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 2.sp, + baselineShift = BaselineShift(-0.05f), + ), + titleMedium = base.titleMedium.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 2.sp, + baselineShift = BaselineShift(-0.06f), + ), + titleSmall = base.titleSmall.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 2.sp, + baselineShift = BaselineShift(-0.06f), + ), + bodyLarge = base.bodyLarge.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.1f), + ), + bodyMedium = base.bodyMedium.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.09f), + ), + bodySmall = base.bodySmall.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.09f), + ), + labelLarge = base.labelLarge.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.07f), + ), + labelMedium = base.labelMedium.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.1f), + ), + labelSmall = base.labelSmall.toDropCapSpan( + sizeRatio = 2f, + antiLetterSpacing = 1.sp, + baselineShift = BaselineShift(-0.1f), + ), + ) + } } fun lexiconTypography( colorScheme: LexiconColors, base: Typography = Typography(), + dropCap: LexiconTypography.DropCapTypography = LexiconTypography.DropCapTypography(base), stamp: TextStyle = base.headlineLarge.copy( fontFamily = stampFontFamily, ), - bodyMediumDropCap: SpanStyle = base.bodyMedium.toDropCapSpan( - sizeRatio = 2f, - antiLetterSpacing = 1.sp, - baselineShift = BaselineShift(-0.1f), - ), - titleMediumDropCap: SpanStyle = base.titleMedium.toDropCapSpan( - sizeRatio = 2f, - antiLetterSpacing = 2.sp, - baselineShift = BaselineShift(-0.05f), - ), - titleLargeDropCap: SpanStyle = base.titleLarge.toDropCapSpan( - sizeRatio = 2f, - antiLetterSpacing = 2.sp, - baselineShift = BaselineShift(-0.05f), - ), - headlineSmallDropCap: SpanStyle = base.headlineSmall.toDropCapSpan( - sizeRatio = 1.8f, - antiLetterSpacing = 4.sp, - ), - headlineLargeDropCap: SpanStyle = base.headlineLarge.toDropCapSpan( - sizeRatio = 1.6f, - antiLetterSpacing = 4.sp, - ), detail: LexiconTypography.Detail = LexiconTypography.Detail( highlightStyle = SpanStyle(color = colorScheme.search.highlight), ), @@ -113,12 +185,8 @@ fun lexiconTypography( ), ): LexiconTypography = LexiconTypography( base = base, + dropCap = dropCap, stamp = stamp, - bodyMediumDropCap = bodyMediumDropCap, - titleMediumDropCap = titleMediumDropCap, - titleLargeDropCap = titleLargeDropCap, - headlineSmallDropCap = headlineSmallDropCap, - headlineLargeDropCap = headlineLargeDropCap, detail = detail, search = search, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/AnnotatedStringHelper.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/AnnotatedStringHelper.kt index a8a8361..44895dc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/AnnotatedStringHelper.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/AnnotatedStringHelper.kt @@ -21,8 +21,12 @@ private val List.extractSentencePattern: String private val dropCapPattern: String get() = "(?:^|\n\n)([A-Z])" +private val majCapsPattern: String + get() = "[A-Z]" + // Specific optimization for the DropCap regex. private val dropCapRegex = Regex(pattern = dropCapPattern) +private val majCapsRegex = Regex(pattern = majCapsPattern) @Immutable @Stable @@ -73,6 +77,15 @@ fun annotateWithDropCap( dropCapRegex styleWith style, ) +@Stable +fun annotateMajWithDropCap( + text: String, + style: SpanStyle, +) = annotate( + text = text, + majCapsRegex styleWith style, +) + @Stable fun dropCapRegex(): Regex = dropCapRegex diff --git a/app/src/main/res/drawable/im_naderius.webp b/app/src/main/res/drawable/im_naderius.webp new file mode 100644 index 0000000000000000000000000000000000000000..bb1c93f4e0af7ee0dd28bd47b7361edb9a3ceb8c GIT binary patch literal 28700 zcmWIYbaN9aU|Tk1j+{^6ue4OXvd(@8M^Ne}RZ$^ne zdH83!%Y^036D4zsly&=T^mc5I*|Pmf>i@@oL!E+Fr29|snz8MmO8M~>hwAs7EdPDw z-6Z$7mpneE{bQH&RKEBkjOUPMN_Y}~v*qC>cKcXa)11WI=bA;R{n*vK=7Icv@sv8z z8f8!{Hic3C|;?$9is5e7R=x&Cv9D?um6`LQ+hVe~HHg<`l^n zDsEr=?;+opONXA9bBL%*8W;I3m>Bce#y>?d$x&84v8eHY^tsc@x-*;Chp(Rc`N1Up zZMTlcyqLUgZz*4$q1c|2K^h+wdR70_zGI!Vb^YgSQ^msHoGJ`Yey=KaYQ#;QC;U|QJgvU)<89ZOth)Qoe{DC? zTOGToo#(i+ro52S&q<;AZmcVEZ~YH>BE8tK-QrBnS@FO79+}T83(wcKK2=zA^XyrZ z6a#IOoheblnPp2{mTgUVW4U?#T(!>9|8+hb*%jJuDs^|}-Hx1{czT(aD#sM1?au{^ znhHZ5`9&kvp3X1&vrSg;C3~;@BtP4uvV96x(wpD#*Xv6~IIlGhc2?njG-1!)NyZM^ zLGL!X*M6IGJUehnb1K{ONrheemu)IN5I^DR-%BnNnf8f)i2m?8@s>+rPDHZ!;n;tN z53zJ#w$SNeSz?hg>v7d?p(2e(xs@eCv$K9R1ZA&ReqOA!rHq*)?!%(Lz56t8ROaw) zmR~<-YIM&HWs&-i(+*AhC^&6*z&ib;=bwMQdU}2jrG+hcP#tv4-WqiQmPXWP4<3Fp%m zsw_Bh@|y1hamV*dJbG=d+OydHZ#u{HfLSdquGaD4R?7vSeY(4Iial&T*@n+*Xzt9g zS$BmZ++B+IW#~z0Nm*8!dV80jBd&egJ|NWr8itzLLKM%6ks0k?k zvp-a;@qC}`|AgDtTfOgIk$jk<_rXl%&*Ta3Tx@r5v)DQ9iNX;jmOH#Fd46at%$(=? zr!j8VkKeP@QmjSq>n2!oTU&ptl#Khed-?L^7na}t{%OV=p|twNZ={Ye_&pLoa&PX@ zZ)-iBx4$?1p2NVpPUfn{w%YsLMfw*l%FZ0xvu@v!yt^SzJGA-Im$3N-+fHtN8L?3M zLa6ADoAcA%=8D}}Tw7)MQ(%ta)U|B-bun6N*IxIYY~1zZYJk}44VME9YTcF9XIKmT z{}Mg*!z=M8@;4czr>W%l&c0JE;jCkR(Kz00>%XHv-#9HV6?6C)z1q)7(ez1^q1Abp zuX0s9k}4^YlaF7U9p~S6^UM6EwSm{t7!*!DI(97J(!#ohcM|U?t1Mr+hd0;GbauZI z`%|l5UizmD>>l1sNo6z>7qq#(ZLN`kt6cw&mx~ovKk>7%Z_i|_{kzlhqQcu1?b04$ z<#`Q1SF18uyykl${Cnk@FB?`FK9V_d`SXI^wk)?_9QhT%6dX|~;94nqn6c)gvuwfV z$Zki?pMN%&S}T1%XcuJl=icgV`#GN{n?KycslmanX!y6CZzYewZP&;AYB^6hoHCmxiyvg~ z_?-VRzVnL3nW;CFA9qL0iR^OD`nMv{+%&u(-eA|ZOXfS*RV=b!AuZ?cliFJ9BjxdT zd0T%IuaKI}+=p(HmsMVza6I7ghD$|COna(0rgt$`-LByhyJhI&Em$ELaPMrxS+nq+ z&Ah8}PxNeCv}!{_C%3uX+v1NEyuqQK>#Js-Ry?tXZI<#c$xmNA)jhlVw>L3#JUe<< zP~KKse6>nTk8Gc&pUkW6mpUIMZI)Vfdd(5G<-7VhmaWcD(GZkgY?AFW_KL%WMI37$ zbaX1-Hr;Z{FDQG#?@E;w&ts=EZ28@*`I>#1UORKp%Tq_Ysxmm7x71YJws*bZp)*VB z*9FGwf|vJ7W^T1~YwvhAx$A^l>+zmPtp_-~URl0q%0A?HP43sx@3S0FJhoc=^gZuq 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 65086b8..8bf21c7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,29 +1,29 @@ Rp-Compagnon - Fermer - Autres + Fermer + Autres - Une erreur s\'est produite. - La structure des données semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'actions semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'altération semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille des personnages semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'actions (object) semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application. - La structure de la feuille d\'ordre des catégories semble avoir changé et n\'est plus compatible avec cette application. - La structure du lexique semble avoir changé et n\'est plus compatible avec cette application. - La structure des cartes semble avoir changé et n\'est plus compatible avec cette application. - La structure des quêtes semble avoir changé et n\'est plus compatible avec cette application. + Une erreur s\'est produite. + La structure des données semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'actions semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'altération semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille des personnages semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'actions (object) semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'ordre des catégories semble avoir changé et n\'est plus compatible avec cette application. + La structure du lexique semble avoir changé et n\'est plus compatible avec cette application. + La structure des cartes semble avoir changé et n\'est plus compatible avec cette application. + La structure des quêtes semble avoir changé et n\'est plus compatible avec cette application. - Succès - SUCCÈS CRITIQUE - Échec - ÉCHEC CRITIQUE + Succès + SUCCÈS CRITIQUE + Échec + ÉCHEC CRITIQUE Abjuration Divination @@ -34,20 +34,33 @@ Nécromancie Transmutation - Se connecter avec + Barbare + Barde + Clerc + Druide + Guerrier + Moine + Paladin + Rôdeur + Roublard + Ensorceleur + Occultiste + Magicien - Lexique - Journal de quêtes - Cartes + Se connecter avec - Rechercher + Lexique + Journal de quêtes + Cartes - Détails du personnage - Description : - Statut : - Localisation : - Histoire : - Portrait : + Rechercher + + Détails du personnage + Description : + Statut : + Localisation : + Histoire : + Portrait : Rechercher Lexique @@ -74,17 +87,17 @@ Durée : Description : - Détails de quête - Complétée - Commanditaire : - Lieu : - Récompense individuelle : - Récompense de groupe : + Détails de quête + Complétée + Commanditaire : + Lieu : + Récompense individuelle : + Récompense de groupe : - Carte - Coordonnées - Destinations : - Illustrations : + Carte + Coordonnées + Destinations : + Illustrations : Rafraichir Talents @@ -199,7 +212,7 @@ Source : %1$s Cible : %1$s - Aucune description disponnible + Aucune description disponnible Tête Visage @@ -216,4 +229,9 @@ Main droite Main gauche Aucune description n\'est attachée à cet equipement + + Game Master + Feuilles de personnage + Encyclopédie + Outils \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1128384..85bd73e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,29 +1,25 @@ Rp-Companion - Close - Others + An error occurred. + The file structure appears to have changed and is no longer compatible with this application + The action sheet structure appears to have changed and is no longer compatible with this application + The alteration sheet structure appears to have changed and is no longer compatible with this application + The character sheet structure appears to have changed and is no longer compatible with this application + The description sheet structure appears to have changed and is no longer compatible with this application + The equipment sheet structure appears to have changed and is no longer compatible with this application + The inventory sheet structure appears to have changed and is no longer compatible with this application + The objects sheet structure appears to have changed and is no longer compatible with this application + The skill sheet structure appears to have changed and is no longer compatible with this application + The spell sheet structure appears to have changed and is no longer compatible with this application + The category order sheet structure appears to have changed and is no longer compatible with this application + The lexicon sheet structure appears to have changed and is no longer compatible with this application + The location sheet structure appears to have changed and is no longer compatible with this application + The quest sheet structure appears to have changed and is no longer compatible with this application - An error occurred. - The file structure appears to have changed and is no longer compatible with this application - The action sheet structure appears to have changed and is no longer compatible with this application - The alteration sheet structure appears to have changed and is no longer compatible with this application - The character sheet structure appears to have changed and is no longer compatible with this application - The description sheet structure appears to have changed and is no longer compatible with this application - The equipment sheet structure appears to have changed and is no longer compatible with this application - The inventory sheet structure appears to have changed and is no longer compatible with this application - The objects sheet structure appears to have changed and is no longer compatible with this application - The skill sheet structure appears to have changed and is no longer compatible with this application - The spell sheet structure appears to have changed and is no longer compatible with this application - The category order sheet structure appears to have changed and is no longer compatible with this application - The lexicon sheet structure appears to have changed and is no longer compatible with this application - The location sheet structure appears to have changed and is no longer compatible with this application - The quest sheet structure appears to have changed and is no longer compatible with this application - - Success - CRITICAL SUCCESS - Failure - CRITICAL FAILURE + Others + Search + No available description Abjuration Divination @@ -34,20 +30,52 @@ Necromancy Transmutation - Sign in with + Barbarian + Bard + Cleric + Druid + Fighter + Monk + Paladin + Ranger + Rogue + Sorcerer + Warlock + Wizard - Lexicon - Quest logs - Location + Sign in with - Search + Curse of Strahd + Character\'s sheets + Tools + Encyclopedia + Brulkhai + Léandre + Nelia + Tigrane + Unathana - Character\'s details - Description: - Status: - Location: - History: - Portrait: + Lexicon + Character\'s details + Description: + Status: + Location: + History: + Portrait: + + Quest logs + Quest details + Completed + Quest giver: + Area: + Individual reward: + Group reward: + + Location + Map + Destinations: + Illustrations: + Coordinates Search Lexicon @@ -74,17 +102,8 @@ Duration: Description: - Quest details - Completed - Quest giver: - Area: - Individual reward: - Group reward: - - Map - Coordinates - Destinations: - Illustrations: + Success + Failure Refresh Proficiencies @@ -162,6 +181,10 @@ Cantrip Spell level %1$s + Close + CRITICAL SUCCESS + CRITICAL FAILURE + %1$s proficiency %1$s expertise Saving throw @@ -199,8 +222,6 @@ Source: %1$s Target: %1$s - No available description - Head face Shoulder @@ -217,8 +238,5 @@ Off hand This equipment does not have any description - Game Master - Statistic - Combat - Rolls + Game Master \ No newline at end of file