diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 5888346..14b99d2 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -69,18 +69,17 @@ kotlin { val desktopMain by getting androidMain.dependencies { - implementation(compose.preview) implementation(libs.androidx.activity.compose) implementation(libs.koin.android) implementation(libs.koin.androidx.compose) - implementation(compose.components.uiToolingPreview) } commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) + implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) + implementation(compose.materialIconsExtended) implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/CodeExampleBoxPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/CodeExampleBoxPreview.kt new file mode 100644 index 0000000..ef23f76 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/CodeExampleBoxPreview.kt @@ -0,0 +1,20 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.CodeExampleBox +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme + +@PreviewLightDark +@Composable +fun CodeExampleBoxPreview() { + KotlinDictionaryTheme { + CodeExampleBox( + code = sampleCodeSnippet(), + modifier = Modifier.padding(16.dp), + ) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewDetailScreen.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewDetailScreen.kt new file mode 100644 index 0000000..cb396ff --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewDetailScreen.kt @@ -0,0 +1,14 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailScreenContent +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme + +@PreviewLightDark +@Composable +fun DetailScreenPreview() { + KotlinDictionaryTheme { + DetailScreenContent(topic = fakeTopicDetails()) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewUtils.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewUtils.kt new file mode 100644 index 0000000..f17da62 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewUtils.kt @@ -0,0 +1,55 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.CodeExample +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Section +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Syntax +import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic + +fun sampleCodeSnippet(): String = """ + fun greet(name: String): String { + return "Hello, Sam!" + } + """.trimIndent() + +fun topic() = "Smart Casts" + +fun subtitle() = "Automatic casting of immutable values" + +fun fakeTopicDetails() = KotlinTopicDetails( + topicId = "smart-cast", + topicName = "Smart Cast", + intro = "Smart casting allows the compiler to automatically cast types.", + syntax = Syntax( + signature = "if (x is String) println(x.length)", + notes = "Works only with immutable vars", + ), + sections = listOf( + Section( + heading = "Why Use It?", + content = "Because it’s safer and reduces boilerplate.", + codeExamples = listOf( + CodeExample( + description = "Basic usage of smart cast", + code = """ + fun printLength(obj: Any) { + if (obj is String) { + println(obj.length) // Smart cast + } + } + """.trimIndent(), + ), + ), + ), + ), + pitfalls = listOf("Doesn't work with mutable vars."), + relatedTopics = listOf("Type Checking", "Safe Casts"), +) + +fun sampleTopicList(): List = listOf( + Topic("Smart Casts"), + Topic("Null Safety"), + Topic("Coroutines"), + Topic("Lambdas"), + Topic("Sealed Classes"), +) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/SearchFieldPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/SearchFieldPreview.kt new file mode 100644 index 0000000..6df983d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/SearchFieldPreview.kt @@ -0,0 +1,22 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.SearchField +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme + +@PreviewLightDark +@Composable +fun SearchFieldPreview() { + KotlinDictionaryTheme { + var searchQuery by remember { mutableStateOf("") } + SearchField( + searchQuery = searchQuery, + onQueryChange = { searchQuery = it }, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicCardPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicCardPreview.kt new file mode 100644 index 0000000..8f5d0bd --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicCardPreview.kt @@ -0,0 +1,20 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicCard +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme + +@PreviewLightDark +@Composable +fun TopicCardPreview() { + KotlinDictionaryTheme { + TopicCard( + topic = topic(), + subtitle = subtitle(), + isBookmarked = false, + onBookmarkClick = {}, + onCardClick = {}, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicScreenPreview.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicScreenPreview.kt new file mode 100644 index 0000000..0ecf3a6 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/TopicScreenPreview.kt @@ -0,0 +1,67 @@ +package com.developersbreach.kotlindictionarymultiplatform.previews + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic +import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiState +import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiStateHandler +import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicScreenLayout +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +// Fake ViewModel for previews +class FakeTopicViewModel : TopicViewModelFakeBase() { + override val topics = MutableStateFlow>>(UiState.Success(sampleTopicList())) + override val searchQuery = MutableStateFlow("") + override val filteredTopics = MutableStateFlow(sampleTopicList()) + override val bookmarkedStates = MutableStateFlow(List(sampleTopicList().size) { true }) + + override fun updateSearchQuery(newQuery: String) {} + + override fun toggleBookmark(index: Int) {} +} + +// Base class to avoid needing Android ViewModel +abstract class TopicViewModelFakeBase { + abstract val topics: StateFlow>> + abstract val searchQuery: StateFlow + abstract val filteredTopics: StateFlow> + abstract val bookmarkedStates: StateFlow> + + abstract fun updateSearchQuery(newQuery: String) + + abstract fun toggleBookmark(index: Int) +} + +@Composable +fun TopicScreenContent( + viewModel: TopicViewModelFakeBase, + onTopicClick: (String) -> Unit = {}, +) { + val topicState by viewModel.topics.collectAsState() + val filteredTopics by viewModel.filteredTopics.collectAsState() + val searchQuery by viewModel.searchQuery.collectAsState() + val bookmarkedStates by viewModel.bookmarkedStates.collectAsState() + + UiStateHandler(uiState = topicState) { + TopicScreenLayout( + topics = filteredTopics, + bookmarkedStates = bookmarkedStates, + searchQuery = searchQuery, + onQueryChange = viewModel::updateSearchQuery, + onBookmarkClick = viewModel::toggleBookmark, + onTopicClick = onTopicClick, + ) + } +} + +@PreviewLightDark +@Composable +fun TopicScreenPreview() { + KotlinDictionaryTheme { + TopicScreenContent(viewModel = FakeTopicViewModel()) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/font/noto_sans_bold.ttf b/composeApp/src/commonMain/composeResources/font/noto_sans_bold.ttf new file mode 100644 index 0000000..6e00cdc Binary files /dev/null and b/composeApp/src/commonMain/composeResources/font/noto_sans_bold.ttf differ diff --git a/composeApp/src/commonMain/composeResources/font/noto_sans_medium.ttf b/composeApp/src/commonMain/composeResources/font/noto_sans_medium.ttf new file mode 100644 index 0000000..25050f7 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/font/noto_sans_medium.ttf differ diff --git a/composeApp/src/commonMain/composeResources/font/noto_sans_regular.ttf b/composeApp/src/commonMain/composeResources/font/noto_sans_regular.ttf new file mode 100644 index 0000000..9dd1019 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/font/noto_sans_regular.ttf differ diff --git a/composeApp/src/commonMain/composeResources/values/string.xml b/composeApp/src/commonMain/composeResources/values/string.xml index 67f5a13..4afcf80 100644 --- a/composeApp/src/commonMain/composeResources/values/string.xml +++ b/composeApp/src/commonMain/composeResources/values/string.xml @@ -3,4 +3,28 @@ Error occurred OK Error information not available + Table of Contents + Back + • Introduction + • Syntax + • Sections + • Pitfalls + • Related Topics + Introduction + Syntax + Sections + Pitfalls + Related Topics + Notes: %1$s + • %1$s + Kotlin + Copy + Copied + Topics + Description need to be added + Icon + Remove bookmark + Add bookmark + Search Kotlin terms... + Search \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/App.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/App.kt index 17984d4..8466e29 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/App.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/App.kt @@ -1,12 +1,12 @@ package com.developersbreach.kotlindictionarymultiplatform -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import com.developersbreach.kotlindictionarymultiplatform.ui.navigation.AppNavigation +import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme @Composable fun App() { - MaterialTheme { + KotlinDictionaryTheme { AppNavigation() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/KotlinTopicDetails.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/KotlinTopicDetails.kt index 149002c..7c4d944 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/KotlinTopicDetails.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/KotlinTopicDetails.kt @@ -1,7 +1,7 @@ package com.developersbreach.kotlindictionarymultiplatform.data.detail.model -import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @Serializable diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/ShowAlertDialog.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/ShowAlertDialog.kt index 75a8737..2e75a12 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/ShowAlertDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/ShowAlertDialog.kt @@ -1,8 +1,8 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.components -import androidx.compose.material.AlertDialog -import androidx.compose.material.Text -import androidx.compose.material.TextButton +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import kotlindictionarymultiplatform.composeapp.generated.resources.Res diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/UiStateHandler.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/UiStateHandler.kt index 20a3a91..9ef0afc 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/UiStateHandler.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/components/UiStateHandler.kt @@ -2,14 +2,14 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.mutableStateOf +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.runtime.saveable.rememberSaveable import kotlindictionarymultiplatform.composeapp.generated.resources.Res import kotlindictionarymultiplatform.composeapp.generated.resources.error_info_unavailable import kotlindictionarymultiplatform.composeapp.generated.resources.error_occurred @@ -32,7 +32,7 @@ fun UiStateHandler( is UiState.Loading -> { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colors.onBackground, + color = MaterialTheme.colorScheme.onBackground, ) } @@ -54,7 +54,7 @@ fun UiStateHandler( if (isLoading) { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colors.onBackground, + color = MaterialTheme.colorScheme.onBackground, ) } } 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 d677050..3f5f88b 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 @@ -1,68 +1,279 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.ContentCopy +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.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.foundation.lazy.items +import androidx.compose.ui.unit.sp +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiStateHandler +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.back +import kotlindictionarymultiplatform.composeapp.generated.resources.bullet_item +import kotlindictionarymultiplatform.composeapp.generated.resources.copied +import kotlindictionarymultiplatform.composeapp.generated.resources.copy +import kotlindictionarymultiplatform.composeapp.generated.resources.introduction +import kotlindictionarymultiplatform.composeapp.generated.resources.introduction_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.kotlin +import kotlindictionarymultiplatform.composeapp.generated.resources.notes_with_value +import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls +import kotlindictionarymultiplatform.composeapp.generated.resources.pitfalls_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics +import kotlindictionarymultiplatform.composeapp.generated.resources.related_topics_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.sections_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.syntax +import kotlindictionarymultiplatform.composeapp.generated.resources.syntax_bullet +import kotlindictionarymultiplatform.composeapp.generated.resources.table_of_contents +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.stringResource @Composable fun DetailScreen(viewModel: DetailViewModel) { - val uiState by viewModel.state.collectAsState() - - UiStateHandler(uiState = uiState) { topic -> - LazyColumn(modifier = Modifier.padding(16.dp)) { - item { - Text(text = topic.topicName, style = MaterialTheme.typography.h5) - Spacer(modifier = Modifier.height(8.dp)) - Text(text = topic.intro) - Spacer(modifier = Modifier.height(16.dp)) + val topicState by viewModel.state.collectAsState() + + UiStateHandler(uiState = topicState) { topic -> + DetailScreenContent(topic) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailScreenContent(topic: KotlinTopicDetails) { + val scrollState = rememberScrollState() + Scaffold( + containerColor = MaterialTheme.colorScheme.background, + topBar = { + TopAppBar( + title = { + Text( + text = topic.topicName, + style = MaterialTheme.typography.displayMedium, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.fillMaxWidth(), + ) + }, + navigationIcon = { + IconButton(onClick = { }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(Res.string.back)) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp) + .padding(innerPadding), + ) { + Text( + text = stringResource(Res.string.table_of_contents), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(modifier = Modifier.height(4.dp)) + + val tableOfContents = 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)) + } + + tableOfContents.forEach { Text( - text = "Signature: ${topic.syntax.signature}", - style = MaterialTheme.typography.subtitle1, + text = it, + modifier = Modifier + .clickable { } + .padding(vertical = 4.dp), + color = MaterialTheme.colorScheme.onSurface, ) - topic.syntax.notes?.let { - Text(text = "Notes: $it") - } - Spacer(modifier = Modifier.height(16.dp)) } - items(topic.sections) { section -> - section.heading?.let { Text(text = it, style = MaterialTheme.typography.h6) } - section.content?.let { Text(text = it) } + Spacer(modifier = Modifier.height(16.dp)) - section.codeExamples.forEach { example -> - example.description?.let { Text(text = "Description: $it") } - Text(text = example.code, style = MaterialTheme.typography.body2) + Text(stringResource(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) + + Spacer(modifier = Modifier.height(16.dp)) + + Text(stringResource(Res.string.syntax), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onPrimary) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = topic.syntax.signature, style = MaterialTheme.typography.bodyMedium) + topic.syntax.notes?.let { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(Res.string.notes_with_value, it), + style = MaterialTheme.typography.bodyMedium, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + topic.sections.forEach { section -> + section.heading?.let { + Text(it, style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onPrimary) + Spacer(modifier = Modifier.height(4.dp)) + } + section.content?.let { + Text(it, style = MaterialTheme.typography.bodyMedium) Spacer(modifier = Modifier.height(8.dp)) } - - Spacer(modifier = Modifier.height(16.dp)) + section.codeExamples.forEach { example -> + example.description?.let { + Text( + it, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyMedium, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + CodeExampleBox(code = example.code) + Spacer(modifier = Modifier.height(16.dp)) + } } if (topic.pitfalls.isNotEmpty()) { - item { - Text(text = "Pitfalls", style = MaterialTheme.typography.h6) - topic.pitfalls.forEach { Text("- $it") } + Text(stringResource(Res.string.pitfalls), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onPrimary) + Spacer(modifier = Modifier.height(4.dp)) + topic.pitfalls.forEach { + Text(stringResource(Res.string.bullet_item, it), style = MaterialTheme.typography.bodyMedium) } + Spacer(modifier = Modifier.height(16.dp)) } if (topic.relatedTopics.isNotEmpty()) { - item { - Spacer(modifier = Modifier.height(16.dp)) - Text(text = "Related Topics", style = MaterialTheme.typography.h6) - topic.relatedTopics.forEach { Text("• $it") } + Text(stringResource(Res.string.related_topics), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onPrimary) + Spacer(modifier = Modifier.height(4.dp)) + topic.relatedTopics.forEach { + Text(stringResource(Res.string.bullet_item, it), style = MaterialTheme.typography.bodyMedium) + } + } + } + } +} + +@Composable +fun CodeExampleBox( + code: String, + modifier: Modifier = Modifier, +) { + val clipboardManager = LocalClipboardManager.current + var copied by remember { mutableStateOf(false) } + + Box( + modifier = modifier + .fillMaxWidth() + .border(1.dp, MaterialTheme.colorScheme.onPrimaryContainer, RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(8.dp)), + ) { + Column(modifier = Modifier.padding(0.dp)) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.onPrimaryContainer, RoundedCornerShape(8.dp)) + .padding(6.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(Res.string.kotlin), + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + modifier = Modifier.padding(start = 4.dp), + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable { + clipboardManager.setText(AnnotatedString(code)) + copied = true + CoroutineScope(Dispatchers.Main).launch { + delay(2500) + copied = false + } + } + .padding(4.dp), + ) { + Icon( + imageVector = if (copied) Icons.Default.Check else Icons.Default.ContentCopy, + contentDescription = stringResource(Res.string.copy), + tint = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = if (copied) stringResource(Res.string.copied) else stringResource(Res.string.copy), + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onPrimary, + ) + } } } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = code, + modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp), + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + lineHeight = 20.sp, + color = MaterialTheme.colorScheme.onPrimary, + ) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicScreen.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicScreen.kt index 5c208cf..07927dd 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicScreen.kt @@ -1,64 +1,262 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +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.fillMaxSize 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.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Card -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Bookmark +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.Bookmark +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.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.developersbreach.kotlindictionarymultiplatform.Log +import androidx.compose.ui.unit.sp import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiStateHandler +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.add_bookmark +import kotlindictionarymultiplatform.composeapp.generated.resources.back +import kotlindictionarymultiplatform.composeapp.generated.resources.description_subtitle +import kotlindictionarymultiplatform.composeapp.generated.resources.icon +import kotlindictionarymultiplatform.composeapp.generated.resources.remove_bookmark +import kotlindictionarymultiplatform.composeapp.generated.resources.search +import kotlindictionarymultiplatform.composeapp.generated.resources.search_kotlin_terms +import kotlindictionarymultiplatform.composeapp.generated.resources.topics +import org.jetbrains.compose.resources.stringResource @Composable fun TopicScreen( onTopicClick: (String) -> Unit, viewModel: TopicViewModel, ) { - val state by viewModel.topics.collectAsState() + val topicState by viewModel.topics.collectAsState() + val filteredTopics by viewModel.filteredTopics.collectAsState() + val searchQuery by viewModel.searchQuery.collectAsState() + val bookmarkedStates by viewModel.bookmarkedStates.collectAsState() - UiStateHandler(uiState = state) { topics -> - LazyColumn( + UiStateHandler(uiState = topicState) { + TopicScreenLayout( + topics = filteredTopics, + bookmarkedStates = bookmarkedStates, + searchQuery = searchQuery, + onQueryChange = viewModel::updateSearchQuery, + onBookmarkClick = viewModel::toggleBookmark, + onTopicClick = onTopicClick, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopicScreenLayout( + topics: List, + bookmarkedStates: List, + searchQuery: String, + onQueryChange: (String) -> Unit, + onBookmarkClick: (Int) -> Unit, + onTopicClick: (String) -> Unit, +) { + Scaffold( + containerColor = MaterialTheme.colorScheme.background, + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(Res.string.topics), + style = MaterialTheme.typography.displayMedium, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Start, + ) + }, + navigationIcon = { + IconButton(onClick = { /* Back action */ }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(Res.string.back)) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) + }, + ) { paddingValues -> + Column( modifier = Modifier - .fillMaxSize() - .padding(16.dp), + .padding(horizontal = 16.dp) + .padding(top = paddingValues.calculateTopPadding()), ) { - items(topics) { topic -> - TopicItem(topic = topic, onClick = { - Log.i("TopicScreen", "Topic clicked: ${topic.name}") - onTopicClick(topic.name) - }) + SearchField(searchQuery = searchQuery, onQueryChange = onQueryChange) + Spacer(modifier = Modifier.height(8.dp)) + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 40.dp), + ) { + itemsIndexed(topics) { index, topic -> + val isBookmarked = bookmarkedStates.getOrNull(index) ?: true + TopicCard( + topic = topic.name, + subtitle = stringResource(Res.string.description_subtitle), + isBookmarked = isBookmarked, + onBookmarkClick = { onBookmarkClick(index) }, + onCardClick = { onTopicClick(topic.name) }, + ) + } } } } } @Composable -fun TopicItem( - topic: Topic, - onClick: () -> Unit, +fun TopicCard( + topic: String, + subtitle: String, + isBookmarked: Boolean, + onBookmarkClick: () -> Unit, + onCardClick: () -> Unit, ) { - Log.i("TopicItem", "Rendering topic item: ${topic.name}") - - Card( - elevation = 4.dp, + Surface( modifier = Modifier .fillMaxWidth() - .padding(vertical = 8.dp) - .clickable { onClick() }, + .padding(8.dp) + .shadow(6.dp, RoundedCornerShape(16.dp), clip = true) + .clickable { onCardClick() }, + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colorScheme.surface, ) { - Text( - text = topic.name, - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(16.dp), - ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(36.dp) + .background(MaterialTheme.colorScheme.primary, CircleShape), + contentAlignment = Alignment.Center, + ) { + Icon(Icons.Filled.Search, contentDescription = stringResource(Res.string.icon)) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = topic, + style = MaterialTheme.typography.headlineMedium.copy( + color = MaterialTheme.colorScheme.onPrimary, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.labelMedium.copy( + color = MaterialTheme.colorScheme.onBackground, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + IconButton(onClick = onBookmarkClick) { + Icon( + imageVector = if (isBookmarked) Icons.Outlined.Bookmark else Icons.Filled.Bookmark, + contentDescription = if (isBookmarked) stringResource(Res.string.remove_bookmark) else stringResource(Res.string.add_bookmark), + tint = MaterialTheme.colorScheme.primary, + ) + } + } } +} + +@Composable +fun SearchField( + searchQuery: String, + onQueryChange: (String) -> Unit, +) { + TextField( + value = searchQuery, + onValueChange = onQueryChange, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(25.dp)) + .padding(4.dp), + placeholder = { + Text( + stringResource(Res.string.search_kotlin_terms), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onBackground, + ) + }, + leadingIcon = { + Icon(Icons.Filled.Search, contentDescription = stringResource(Res.string.search), tint = MaterialTheme.colorScheme.onBackground) + }, + colors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, + cursorColor = MaterialTheme.colorScheme.onBackground, + focusedTextColor = MaterialTheme.colorScheme.onBackground, + unfocusedTextColor = MaterialTheme.colorScheme.onBackground, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), + shape = RoundedCornerShape(25.dp), + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + // Optional: handle enter key logic + }, + ), + textStyle = MaterialTheme.typography.titleMedium.copy( + color = MaterialTheme.colorScheme.onBackground, + lineHeight = 24.sp, + letterSpacing = .5.sp, + ), + ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicViewModel.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicViewModel.kt index 145dfbb..89d31df 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicViewModel.kt @@ -7,7 +7,11 @@ import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic import com.developersbreach.kotlindictionarymultiplatform.data.topic.repository.TopicRepository import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiState import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.io.IOException @@ -18,6 +22,23 @@ class TopicViewModel( private val _topics = MutableStateFlow>>(UiState.Loading) val topics: StateFlow>> = _topics + private val _searchQuery = MutableStateFlow("") + val searchQuery: StateFlow = _searchQuery + + private val _bookmarkedStates = MutableStateFlow>(emptyList()) + val bookmarkedStates: StateFlow> = _bookmarkedStates + + val filteredTopics: StateFlow> = combine(_topics, _searchQuery) { uiState, query -> + val allTopics = (uiState as? UiState.Success)?.data ?: return@combine emptyList() + if (query.isBlank()) { + allTopics + } else { + allTopics.filter { + it.name.contains(query, ignoreCase = true) + } + } + }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + init { viewModelScope.launch { try { @@ -29,9 +50,27 @@ class TopicViewModel( } private fun fetchTopicList() { - _topics.value = repository.getTopics().fold( + val result = repository.getTopics() + _topics.value = result.fold( ifLeft = { UiState.Error(it) }, - ifRight = { UiState.Success(it) }, + ifRight = { + _bookmarkedStates.value = List(it.size) { true } + UiState.Success(it) + }, ) } + + fun updateSearchQuery(newQuery: String) { + _searchQuery.value = newQuery + } + + fun toggleBookmark(index: Int) { + _bookmarkedStates.update { current -> + if (index in current.indices) { + current.toMutableList().apply { this[index] = !this[index] } + } else { + current + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Colors.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Colors.kt new file mode 100644 index 0000000..a3d1fc9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Colors.kt @@ -0,0 +1,25 @@ +package com.developersbreach.kotlindictionarymultiplatform.ui.theme + +import androidx.compose.ui.graphics.Color + +// Light Theme Colors +val light_primary = Color(0xFFFFE0B2) +val light_onPrimary = Color(0xFF0F2851) +val light_background = Color(0xFFFEF9F9) +val light_onBackground = Color(0xFF5A5A5A) +val light_surface = Color(0xFFEFEBE8) +val light_onSurface = Color(0xFF0F2851) +val light_surfaceVariant = Color(0xFFE0E0E0) +val light_primaryContainer = Color(0xFFFFE0B2) +val light_onPrimaryContainer = Color(0xFFECB77A) + +// Dark Theme Colors +val dark_primary = Color(0xFFFFCF8A) +val dark_onPrimary = Color(0xFFFAFAFA) +val dark_background = Color(0xFF1F1F1F) +val dark_onBackground = Color(0xFF939393) +val dark_surface = Color(0x4A4A4A) +val dark_onSurface = Color(0xFFFAFAFA) +val dark_surfaceVariant = Color(0xFF2A2A2A) +val dark_primaryContainer = Color(0xFF616161) +val dark_onPrimaryContainer = Color(0xFF343434) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Font.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Font.kt new file mode 100644 index 0000000..7364e4e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Font.kt @@ -0,0 +1,38 @@ +package com.developersbreach.kotlindictionarymultiplatform.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import kotlindictionarymultiplatform.composeapp.generated.resources.Res +import kotlindictionarymultiplatform.composeapp.generated.resources.noto_sans_bold +import kotlindictionarymultiplatform.composeapp.generated.resources.noto_sans_medium +import kotlindictionarymultiplatform.composeapp.generated.resources.noto_sans_regular +import org.jetbrains.compose.resources.Font + +@Composable +fun AppTypography(): Typography { + val fontFamily = FontFamily( + Font(Res.font.noto_sans_regular, weight = FontWeight.Normal), + Font(Res.font.noto_sans_medium, weight = FontWeight.Medium), + Font(Res.font.noto_sans_bold, weight = FontWeight.Bold), + ) + + return Typography( + displayLarge = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Bold, fontSize = 30.sp), + displayMedium = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Bold, fontSize = 22.sp), + displaySmall = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Medium, fontSize = 24.sp), + headlineLarge = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Bold, fontSize = 18.sp, lineHeight = (18 * 1.3).sp, letterSpacing = (18 * 0.012).sp), + headlineMedium = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Medium, fontSize = 18.sp, lineHeight = (18 * 1.3).sp, letterSpacing = (18 * 0.012).sp), + bodyLarge = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp), + bodyMedium = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp), + titleLarge = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Bold, fontSize = 18.sp), + titleMedium = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Medium, fontSize = 16.sp), + titleSmall = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp), + labelLarge = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Bold, fontSize = 14.sp), + labelMedium = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = (18 * 1.3).sp, letterSpacing = (18 * 0.012).sp), + labelSmall = TextStyle(fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 10.sp), + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Theme.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Theme.kt new file mode 100644 index 0000000..b398b30 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/theme/Theme.kt @@ -0,0 +1,45 @@ +package com.developersbreach.kotlindictionarymultiplatform.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val LightColors = lightColorScheme( + primary = light_primary, + onPrimary = light_onPrimary, + background = light_background, + onBackground = light_onBackground, + surface = light_surface, + onSurface = light_onSurface, + surfaceVariant = light_surfaceVariant, + primaryContainer = light_primaryContainer, + onPrimaryContainer = light_onPrimaryContainer, +) + +private val DarkColors = darkColorScheme( + primary = dark_primary, + onPrimary = dark_onPrimary, + background = dark_background, + onBackground = dark_onBackground, + surface = dark_surface, + onSurface = dark_onSurface, + surfaceVariant = dark_surfaceVariant, + primaryContainer = dark_primaryContainer, + onPrimaryContainer = dark_onPrimaryContainer, +) + +@Composable +fun KotlinDictionaryTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colors = if (useDarkTheme) DarkColors else LightColors + + MaterialTheme( + colorScheme = colors, + typography = AppTypography(), + content = content, + ) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3c76ffd..04bfecb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] -agp = "8.6.0" +agp = "8.10.1" android-compileSdk = "35" android-minSdk = "24" android-targetSdk = "35" androidx-activityCompose = "1.10.1" androidx-appcompat = "1.7.0" androidx-constraintlayout = "2.2.1" -androidx-core-ktx = "1.15.0" +androidx-core-ktx = "1.16.0" androidx-espresso-core = "3.6.1" androidx-lifecycle = "2.8.4" androidx-material = "1.12.0" @@ -16,7 +16,7 @@ compose-multiplatform = "1.7.3" junit = "4.13.2" kermit = "2.0.4" kotlin = "2.1.10" -kotlinx-coroutines = "1.10.1" +kotlinx-coroutines = "1.10.2" kotlinx-serialization-json = "1.7.3" ktor-bom = "3.0.1" koin = "4.0.4" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..002b867 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME