diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailContentPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailContentPreview.kt deleted file mode 100644 index 1506c42..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailContentPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailContent -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun DetailContentPreview() { - KotlinDictionaryTheme { - DetailContent(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailScreenPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailScreenPreview.kt index 71b7ea4..05ad985 100644 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailScreenPreview.kt +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailScreenPreview.kt @@ -4,12 +4,16 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.PreviewLightDark import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailScreenUI +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.toDetailUi import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme @PreviewLightDark @Composable fun DetailScreenPreview() { KotlinDictionaryTheme { - DetailScreenUI(topic = fakeTopicDetails()) + DetailScreenUI( + detailUiState = fakeTopicDetails().toDetailUi(), + navigateUp = {}, + ) } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailTopAppBarPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailTopAppBarPreview.kt deleted file mode 100644 index 809203a..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/DetailTopAppBarPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailTopBar -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun DetailTopBarPreview() { - KotlinDictionaryTheme { - DetailTopBar(title = fakeTopicDetails().topicName) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/IntroductionSectionPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/IntroductionSectionPreview.kt deleted file mode 100644 index 0452750..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/IntroductionSectionPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.IntroductionSection -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun IntroductionSectionPreview() { - KotlinDictionaryTheme { - IntroductionSection(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/PitfallsSectionPreviews.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/PitfallsSectionPreviews.kt deleted file mode 100644 index a37a2cb..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/PitfallsSectionPreviews.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.PitfallsSection -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun PitfallsSectionPreview() { - KotlinDictionaryTheme { - PitfallsSection(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/RelatedTopicsSectionPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/RelatedTopicsSectionPreview.kt deleted file mode 100644 index c9be028..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/RelatedTopicsSectionPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.RelatedTopicsSection -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun RelatedTopicsSectionPreview() { - KotlinDictionaryTheme { - RelatedTopicsSection(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SectionsListPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SectionsListPreview.kt deleted file mode 100644 index 5fa52ca..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SectionsListPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.SectionsList -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun SectionsListPreview() { - KotlinDictionaryTheme { - SectionsList(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SyntaxSectionPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SyntaxSectionPreview.kt deleted file mode 100644 index df89e19..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/SyntaxSectionPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.SyntaxSection -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun SyntaxSectionPreview() { - KotlinDictionaryTheme { - SyntaxSection(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/TableOfContentsPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/TableOfContentsPreview.kt deleted file mode 100644 index b9e22c9..0000000 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/detail/TableOfContentsPreview.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.previews.detail - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.developersbreach.kotlindictionarymultiplatform.previews.fakeTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.TableOfContents -import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme - -@PreviewLightDark -@Composable -fun TableOfContentsPreview() { - KotlinDictionaryTheme { - TableOfContents(topic = fakeTopicDetails()) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/navigation/AppNavigation.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/navigation/AppNavigation.kt index 0df1a95..547e993 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/navigation/AppNavigation.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/navigation/AppNavigation.kt @@ -34,7 +34,10 @@ fun AppNavigation( composable { val viewModel: DetailViewModel = koinViewModel() - DetailScreen(viewModel = viewModel) + DetailScreen( + viewModel = viewModel, + navigateUp = { navController.navigateUp() }, + ) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailContent.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailContent.kt deleted file mode 100644 index 501cb24..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailContent.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.IntroductionSection -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.PitfallsSection -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.RelatedTopicsSection -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.SectionsList -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.SyntaxSection -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components.TableOfContents - -@Composable -fun DetailContent( - topic: KotlinTopicDetails, - modifier: Modifier = Modifier, -) { - Column(modifier = modifier.fillMaxSize()) { - TableOfContents(topic) - IntroductionSection(topic) - SyntaxSection(topic) - SectionsList(topic) - PitfallsSection(topic) - RelatedTopicsSection(topic) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreen.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreen.kt index 10cd4e3..779e70d 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreen.kt @@ -8,10 +8,14 @@ import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiStateH @Composable fun DetailScreen( viewModel: DetailViewModel, + navigateUp: () -> Unit, ) { val topicState by viewModel.state.collectAsState() - UiStateHandler(uiState = topicState) { topic -> - DetailScreenUI(topic) + UiStateHandler(uiState = topicState) { detailUiState -> + DetailScreenUI( + detailUiState = detailUiState, + navigateUp = navigateUp, + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenComponents.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenComponents.kt new file mode 100644 index 0000000..7f38b24 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenComponents.kt @@ -0,0 +1,156 @@ +package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Section +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Syntax +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.bullet_item +import kotlindictionarymultiplatform.composeapp.generated.resources.introduction +import kotlindictionarymultiplatform.composeapp.generated.resources.notes_with_value +import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls +import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics +import kotlindictionarymultiplatform.composeapp.generated.resources.syntax +import org.jetbrains.compose.resources.stringResource + +@Composable +fun TableOfContents( + item: String, + onClick: () -> Unit, +) { + Text( + text = item, + modifier = Modifier.clickable { onClick() }, + color = MaterialTheme.colorScheme.onSurface, + ) + Spacer(Modifier.height(4.dp)) +} + +@Composable +fun IntroductionSection( + intro: String, +) { + Text( + text = stringResource(resource = Res.string.introduction), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + Text( + text = intro, + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(Modifier.height(16.dp)) +} + +@Composable +fun SyntaxSection( + syntax: Syntax, +) { + Text( + text = stringResource(resource = Res.string.syntax), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + Text( + text = syntax.signature, + style = MaterialTheme.typography.bodyMedium, + ) + syntax.notes?.let { + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource( + Res.string.notes_with_value, + it, + ), + style = MaterialTheme.typography.bodyMedium, + ) + } + Spacer(Modifier.height(16.dp)) +} + +@Composable +fun SectionBlock( + section: Section, +) { + section.heading?.let { + Text( + text = it, + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + } + + section.content?.let { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(Modifier.height(8.dp)) + } + + section.codeExamples.forEach { example -> + example.description?.let { + Text( + text = it, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyMedium, + ) + } + Spacer(Modifier.height(16.dp)) + CodeExampleBox(code = example.code) + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +fun PitfallsSection( + pitfalls: List, +) { + Text( + text = stringResource(resource = Res.string.pitfalls), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + pitfalls.forEach { + Text( + text = stringResource( + Res.string.bullet_item, + it, + ), + style = MaterialTheme.typography.bodyMedium, + ) + } + Spacer(Modifier.height(16.dp)) +} + +@Composable +fun RelatedTopicsSection( + relatedTopics: List, +) { + Text( + text = stringResource(resource = Res.string.related_topics), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + relatedTopics.forEach { + Text( + text = stringResource( + Res.string.bullet_item, + it, + ), + style = MaterialTheme.typography.bodyMedium, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenUI.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenUI.kt index f9756d2..4a32901 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenUI.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailScreenUI.kt @@ -1,31 +1,98 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.table_of_contents +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.stringResource @Composable fun DetailScreenUI( - topic: KotlinTopicDetails, + detailUiState: DetailUiState, + navigateUp: () -> Unit, ) { - val scrollState = rememberScrollState() - Scaffold( + topBar = { + DetailTopBar( + title = detailUiState.topicName, + navigateUp = navigateUp, + ) + }, containerColor = MaterialTheme.colorScheme.background, - topBar = { DetailTopBar(title = topic.topicName) }, ) { innerPadding -> DetailContent( - topic = topic, - modifier = Modifier - .padding(innerPadding) - .verticalScroll(scrollState) - .padding(16.dp), + detailUiState = detailUiState, + modifier = Modifier.padding(innerPadding), ) } +} + +@Composable +private fun DetailContent( + detailUiState: DetailUiState, + modifier: Modifier = Modifier, +) { + val listState = rememberLazyListState() + val coroutineScope = rememberCoroutineScope() + + LazyColumn( + state = listState, + modifier = modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + ) { + item { + Text( + text = stringResource(Res.string.table_of_contents), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(Modifier.height(4.dp)) + } + + items( + items = detailUiState.tocEntries, + ) { + TableOfContents( + item = stringResource( + resource = it.titleRes, + formatArgs = it.formatArgs.toTypedArray(), + ), + onClick = { + coroutineScope.launch { + listState.animateScrollToItem(it.destinationIndex) + } + }, + ) + } + + item { + Spacer(Modifier.height(16.dp)) + } + + items( + items = detailUiState.contentItems, + ) { toc: ItemTableOfContent -> + when (toc) { + is ItemTableOfContent.Introduction -> IntroductionSection(toc.intro) + is ItemTableOfContent.Syntax -> SyntaxSection(toc.syntax) + is ItemTableOfContent.Section -> SectionBlock(toc.sections) + is ItemTableOfContent.Pitfall -> PitfallsSection(toc.pitfalls) + is ItemTableOfContent.RelatedTopic -> RelatedTopicsSection(toc.relatedTopics) + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailTopAppBar.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailTopAppBar.kt index bc9098f..ad1912f 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailTopAppBar.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailTopAppBar.kt @@ -20,6 +20,7 @@ import org.jetbrains.compose.resources.stringResource @Composable fun DetailTopBar( title: String, + navigateUp: () -> Unit, ) { TopAppBar( title = { @@ -31,7 +32,7 @@ fun DetailTopBar( ) }, navigationIcon = { - IconButton(onClick = { /* TODO: Navigate back */ }) { + IconButton(onClick = navigateUp) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(Res.string.back), diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailUiState.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailUiState.kt new file mode 100644 index 0000000..a5354b5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailUiState.kt @@ -0,0 +1,108 @@ +package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail + +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.bullet_item +import kotlindictionarymultiplatform.composeapp.generated.resources.introduction_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.syntax_bullet +import org.jetbrains.compose.resources.StringResource + +data class DetailUiState( + val tocEntries: List, + val contentItems: List, + val topicName: String, +) + +sealed class TocItem { + data object Header : TocItem() + + data class Toc( + val toc: ItemTableOfContent, + ) : TocItem() + + data class Content( + val toc: ItemTableOfContent, + ) : TocItem() +} + +data class TocEntry( + val titleRes: StringResource, + val formatArgs: List = emptyList(), + val destinationIndex: Int, +) + +sealed class ItemTableOfContent { + + abstract val title: StringResource + + data class Introduction( + val intro: String, + override val title: StringResource = Res.string.introduction_bullet, + ) : ItemTableOfContent() + + data class Syntax( + val syntax: com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Syntax, + override val title: StringResource = Res.string.syntax_bullet, + ) : ItemTableOfContent() + + data class Section( + val index: Int, + val sections: com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Section, + override val title: StringResource = Res.string.bullet_item, + ) : ItemTableOfContent() + + data class Pitfall( + val pitfalls: List, + override val title: StringResource = Res.string.pitfalls_bullet, + ) : ItemTableOfContent() + + data class RelatedTopic( + val relatedTopics: List, + override val title: StringResource = Res.string.related_topics_bullet, + ) : ItemTableOfContent() +} + +fun KotlinTopicDetails.toDetailUi(): DetailUiState { + val contentList = this.toContents() + + val flatList = buildList { + this.add(TocItem.Header) + contentList.forEach { this.add(TocItem.Toc(it)) } + contentList.forEach { this.add(TocItem.Content(it)) } + } + + val tocEntries = contentList.map { tocItem -> + TocEntry( + titleRes = tocItem.title, + formatArgs = when (tocItem) { + is ItemTableOfContent.Section -> listOf(tocItem.sections.heading ?: "") + else -> emptyList() + }, + destinationIndex = flatList.indexOfFirst { + it is TocItem.Content && it.toc == tocItem + }, + ) + } + + return DetailUiState( + tocEntries = tocEntries, + contentItems = contentList, + topicName = topicName, + ) +} + +private fun KotlinTopicDetails.toContents(): List { + return buildList { + add(ItemTableOfContent.Introduction(intro = intro)) + if (syntax.signature.isNotBlank()) add(ItemTableOfContent.Syntax(syntax)) + sections.forEachIndexed { index, section -> + section.heading?.let { + add(ItemTableOfContent.Section(index, section)) + } + } + if (pitfalls.isNotEmpty()) add(ItemTableOfContent.Pitfall(pitfalls)) + if (relatedTopics.isNotEmpty()) add(ItemTableOfContent.RelatedTopic(relatedTopics)) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailViewModel.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailViewModel.kt index 3525326..7801e49 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/DetailViewModel.kt @@ -5,13 +5,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.developersbreach.kotlindictionarymultiplatform.Log -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch import com.developersbreach.kotlindictionarymultiplatform.data.detail.repository.DetailRepository import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiState import com.developersbreach.kotlindictionarymultiplatform.ui.navigation.AppDestinations +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import kotlinx.io.IOException class DetailViewModel( @@ -21,8 +20,8 @@ class DetailViewModel( private val topicId = savedStateHandle.toRoute().topicId - private val _state = MutableStateFlow>(UiState.Loading) - val state: StateFlow> = _state + private val _state = MutableStateFlow>(UiState.Loading) + val state: StateFlow> = _state init { viewModelScope.launch { @@ -37,7 +36,7 @@ class DetailViewModel( private suspend fun fetchTopic() { _state.value = repository.fetchTopic(topicId).fold( ifLeft = { UiState.Error(it) }, - ifRight = { UiState.Success(it) }, + ifRight = { UiState.Success(it.toDetailUi()) }, ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/IntroductionSection.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/IntroductionSection.kt deleted file mode 100644 index 7352794..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/IntroductionSection.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlindictionarymultiplatform.composeapp.generated.resources.Res -import kotlindictionarymultiplatform.composeapp.generated.resources.introduction -import org.jetbrains.compose.resources.stringResource - -@Composable -fun IntroductionSection( - topic: KotlinTopicDetails, -) { - Text( - text = stringResource(resource = Res.string.introduction), - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = topic.intro, - style = MaterialTheme.typography.bodyMedium, - ) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/PitfallsSection.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/PitfallsSection.kt deleted file mode 100644 index 481cb10..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/PitfallsSection.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlindictionarymultiplatform.composeapp.generated.resources.Res -import kotlindictionarymultiplatform.composeapp.generated.resources.bullet_item -import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls -import org.jetbrains.compose.resources.stringResource - -@Composable -fun PitfallsSection( - topic: KotlinTopicDetails, -) { - if (topic.pitfalls.isNotEmpty()) { - Text( - text = stringResource(resource = Res.string.pitfalls), - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(Modifier.height(4.dp)) - topic.pitfalls.forEach { - Text( - text = stringResource( - Res.string.bullet_item, - it, - ), - style = MaterialTheme.typography.bodyMedium, - ) - } - Spacer(Modifier.height(16.dp)) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/RelatedTopicsSection.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/RelatedTopicsSection.kt deleted file mode 100644 index cbeda30..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/RelatedTopicsSection.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlindictionarymultiplatform.composeapp.generated.resources.Res -import kotlindictionarymultiplatform.composeapp.generated.resources.bullet_item -import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics -import org.jetbrains.compose.resources.stringResource - -@Composable -fun RelatedTopicsSection( - topic: KotlinTopicDetails, -) { - if (topic.relatedTopics.isNotEmpty()) { - Text( - text = stringResource(resource = Res.string.related_topics), - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(Modifier.height(4.dp)) - topic.relatedTopics.forEach { - Text( - text = stringResource( - Res.string.bullet_item, - it, - ), - style = MaterialTheme.typography.bodyMedium, - ) - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SectionsList.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SectionsList.kt deleted file mode 100644 index 29d690b..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SectionsList.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.CodeExampleBox - -@Composable -fun SectionsList( - topic: KotlinTopicDetails, -) { - topic.sections.forEach { section -> - section.heading?.let { - Text( - text = it, - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(Modifier.height(4.dp)) - } - section.content?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - ) - Spacer(Modifier.height(8.dp)) - } - section.codeExamples.forEach { example -> - example.description?.let { - Text( - text = it, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyMedium, - ) - } - Spacer(Modifier.height(16.dp)) - CodeExampleBox(code = example.code) - Spacer(Modifier.height(16.dp)) - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SyntaxSection.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SyntaxSection.kt deleted file mode 100644 index e74b255..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/SyntaxSection.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlindictionarymultiplatform.composeapp.generated.resources.Res -import kotlindictionarymultiplatform.composeapp.generated.resources.notes_with_value -import kotlindictionarymultiplatform.composeapp.generated.resources.syntax -import org.jetbrains.compose.resources.stringResource - -@Composable -fun SyntaxSection( - topic: KotlinTopicDetails, -) { - if (topic.syntax.signature.isNotBlank()) { - Text( - text = stringResource(resource = Res.string.syntax), - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(Modifier.height(4.dp)) - Text( - text = topic.syntax.signature, - style = MaterialTheme.typography.bodyMedium, - ) - topic.syntax.notes?.let { - Spacer(Modifier.height(4.dp)) - Text( - text = stringResource( - Res.string.notes_with_value, - it, - ), - style = MaterialTheme.typography.bodyMedium, - ) - } - Spacer(Modifier.height(16.dp)) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/TableOfContents.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/TableOfContents.kt deleted file mode 100644 index 29ad76f..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/detail/components/TableOfContents.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import kotlindictionarymultiplatform.composeapp.generated.resources.Res -import kotlindictionarymultiplatform.composeapp.generated.resources.introduction_bullet -import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls_bullet -import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics_bullet -import kotlindictionarymultiplatform.composeapp.generated.resources.sections_bullet -import kotlindictionarymultiplatform.composeapp.generated.resources.syntax_bullet -import kotlindictionarymultiplatform.composeapp.generated.resources.table_of_contents -import org.jetbrains.compose.resources.stringResource - -@Composable -fun TableOfContents( - topic: KotlinTopicDetails, -) { - val items = buildList { - add(stringResource(Res.string.introduction_bullet)) - if (topic.syntax.signature.isNotBlank()) add(stringResource(Res.string.syntax_bullet)) - if (topic.sections.isNotEmpty()) add(stringResource(Res.string.sections_bullet)) - if (topic.pitfalls.isNotEmpty()) add(stringResource(Res.string.pitfalls_bullet)) - if (topic.relatedTopics.isNotEmpty()) add(stringResource(Res.string.related_topics_bullet)) - } - - Text( - text = stringResource(Res.string.table_of_contents), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(Modifier.height(4.dp)) - items.forEach { - Text( - text = it, - modifier = Modifier - .clickable { /* scroll-to support later */ } - .padding(vertical = 4.dp), - color = MaterialTheme.colorScheme.onSurface, - ) - } - Spacer(Modifier.height(16.dp)) -} \ No newline at end of file