From a8c01311ce5710c728d70fd86c0283a572345441 Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh Date: Mon, 9 Jun 2025 20:28:17 +0530 Subject: [PATCH 1/8] - Replace OpenAI API with Gemini API for generating Kotlin topic details. --- build.gradle.kts | 4 +- composeApp/build.gradle.kts | 8 +- .../Platform.android.kt | 2 +- .../core/network/KtorHttpClient.kt | 173 ++++++++---------- gradle/libs.versions.toml | 6 +- 5 files changed, 93 insertions(+), 100 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1b5110a..42b41e6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,4 +7,6 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.serialization) apply false -} \ No newline at end of file + alias(libs.plugins.googleSecrets) apply false +} + diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ecf2e0d..0188acb 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -14,6 +14,7 @@ plugins { alias(libs.plugins.composeCompiler) alias(libs.plugins.serialization) alias(libs.plugins.ktlint) + alias(libs.plugins.googleSecrets) } ktlint { @@ -74,6 +75,7 @@ kotlin { implementation(libs.androidx.activity.compose) implementation(libs.koin.android) implementation(libs.koin.androidx.compose) + implementation(libs.generativeai) } commonMain.dependencies { implementation(compose.runtime) @@ -164,11 +166,11 @@ fun ApplicationDefaultConfig.setupBuildConfigFields( ) { fun secret(key: String): String = System.getenv(key) ?: properties.getProperty(key, "") - if (secret("OPEN_API_KEY").isEmpty()) { - error("OPEN_API_KEY not set in local.properties") + if (secret("GEMINI_API_KEY").isEmpty()) { + error("GEMINI_API_KEY not set in local.properties") } - buildConfigField(type = "String", name = "OPEN_API_KEY", value = "\"${secret("OPEN_API_KEY")}\"") + buildConfigField(type = "String", name = "GEMINI_API_KEY", value = "\"${secret("GEMINI_API_KEY")}\"") } fun getLocalProperties(): Properties { diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt index 23a9971..a0afb1c 100644 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt @@ -8,4 +8,4 @@ class AndroidPlatform : Platform { actual fun getPlatform(): Platform = AndroidPlatform() -actual fun getOpenApiKey() = BuildConfig.OPEN_API_KEY \ No newline at end of file +actual fun getOpenApiKey() = BuildConfig.GEMINI_API_KEY \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt index 32a4a10..8c29bf4 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt @@ -1,28 +1,31 @@ package com.developersbreach.kotlindictionarymultiplatform.core.network import com.developersbreach.kotlindictionarymultiplatform.Log -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatCompletionRequest -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatCompletionResponse -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatMessage -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.FunctionDefinition import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.setBody -import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray object KtorHttpClient { - private val json = Json { ignoreUnknownKeys = true } + + private val json = Json { + ignoreUnknownKeys = true + prettyPrint = true + } private val client = HttpClient { install(ContentNegotiation) { @@ -30,101 +33,83 @@ object KtorHttpClient { } } - private val functionSchema = json.parseToJsonElement( - // Paste the full JSON schema for your function here - """ - { - "type": "object", - "properties": { - "topicId": { "type": "string" }, - "topicName": { "type": "string" }, - "intro": { "type": "string" }, - "syntax": { - "type": "object", - "properties": { - "signature": { "type": "string" }, - "notes": { "type": "string" } - }, - "required": ["signature"] - }, - "sections": { - "type": "array", - "items": { - "type": "object", - "properties": { - "heading": { "type": "string" }, - "content": { "type": "string" }, - "codeExamples": { - "type": "array", - "items": { - "type": "object", - "properties": { - "description": { "type": "string" }, - "code": { "type": "string" }, - "language": { "type": "string" } - }, - "required": ["code"] - } - } - }, - "required": ["heading","content"] - } - }, - "pitfalls": { "type": "array", "items": { "type": "string" } }, - "relatedTopics": { "type": "array", "items": { "type": "string" } }, - "metadata": { "type": "object", "additionalProperties": true } - }, - "required": ["topicId","topicName","intro","syntax","sections"] - } - """.trimIndent(), - ) - - private val functionDef = FunctionDefinition( - name = "generate_kotlin_topic_details", - description = "Return a fully-featured Kotlin documentation object for a given topic", - parameters = functionSchema, - ) - - /** - * Calls the OpenAI ChatCompletion with function-calling to get topic details. - * @param topicId the topic identifier, e.g. "variables". - * @param apiKey your OpenAI API key. - */ suspend fun generateTopicDetails( topicId: String, apiKey: String, ): KotlinTopicDetails { - // Prepare messages - val messages = listOf( - ChatMessage("system", "You are a Kotlin documentation generator."), - ChatMessage("user", "Generate full Kotlin documentation for topic \"$topicId\"."), - ) + // Gemini-style prompt + val prompt = """ + You are a Kotlin documentation generator. + Generate a JSON object for the topic "$topicId" with the following structure: + + { + "topicId": "...", + "topicName": "...", + "intro": "...", + "syntax": { + "signature": "...", + "notes": "..." + }, + "sections": [ + { + "heading": "...", + "content": "...", + "codeExamples": [ + { + "description": "...", + "code": "...", + "language": "kotlin" + } + ] + } + ], + "pitfalls": ["..."], + "relatedTopics": ["..."], + "metadata": {} + } + + Respond only with pure JSON. No explanation or markdown. + """.trimIndent() - // Build request body - val request = ChatCompletionRequest( - model = "gpt-4o-mini", - messages = messages, - functions = listOf(functionDef), - functionCall = mapOf("name" to functionDef.name), - ) + val requestBody = buildJsonObject { + putJsonArray("contents") { + addJsonObject { + putJsonArray("parts") { + addJsonObject { + put("text", prompt) + } + } + } + } + } - // Execute HTTP request - val response: HttpResponse = client.post("https://api.openai.com/v1/chat/completions") { - header(HttpHeaders.Authorization, "Bearer $apiKey") + val response = client.post("https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey") { contentType(ContentType.Application.Json) - setBody(json.encodeToString(ChatCompletionRequest.serializer(), request)) + setBody(requestBody) } - val text = response.bodyAsText() - Log.i("RawResponse", "RAW RESPONSE:\n$text") + val responseBody = response.bodyAsText() + Log.i("GeminiRawResponse", responseBody) + + // Parse root object + val root = json.parseToJsonElement(responseBody).jsonObject + val candidates = root["candidates"]?.jsonArray ?: error("Missing candidates") + val firstCandidate = candidates.first().jsonObject + val content = firstCandidate["content"]?.jsonObject ?: error("Missing content") + val partsArray = content["parts"]?.jsonArray ?: error("Missing parts array") + val part = partsArray.first().jsonObject + val rawJson = part["text"]?.jsonPrimitive?.content ?: error("Missing text in part") + Log.i("RawJson", rawJson) - // Parse response - val chatResp = json.decodeFromString(ChatCompletionResponse.serializer(), text) - Log.i("ChatResponse", "$chatResp") - val funcCall = chatResp.choices?.first()?.message?.functionCall ?: error("No function call in response") + // Trim whitespace, remove code fences if any + val cleanJson = rawJson.trim() + .removePrefix("```json\n") + .removePrefix("```json") + .removePrefix("json\n") + .removePrefix("json") + .removeSuffix("```") + .trim() - // The arguments field is a JSON string: parse and decode into our DTO - val argsJson = json.parseToJsonElement(funcCall.arguments) - return json.decodeFromJsonElement(argsJson.jsonObject) + return json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson) } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04bfecb..7472abc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ androidx-material = "1.12.0" androidx-test-junit = "1.2.1" arrowFxCoroutines = "2.1.0" compose-multiplatform = "1.7.3" +generativeai = "0.9.0" junit = "4.13.2" kermit = "2.0.4" kotlin = "2.1.10" @@ -22,11 +23,13 @@ ktor-bom = "3.0.1" koin = "4.0.4" navigation-compose = "2.9.0" ktlint = "12.2.0" +secrets = "2.0.1" [libraries] androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrowFxCoroutines" } arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrowFxCoroutines" } +generativeai = { module = "com.google.ai.client.generativeai:generativeai", version.ref = "generativeai" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } @@ -64,4 +67,5 @@ composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-mu composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } \ No newline at end of file +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } +googleSecrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } \ No newline at end of file From bb45203fb702f4f9823b5829f8c279278be578bf Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh Date: Mon, 9 Jun 2025 20:32:34 +0530 Subject: [PATCH 2/8] Remove unused import from `KtorHttpClient.kt`. --- .../kotlindictionarymultiplatform/core/network/KtorHttpClient.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt index 8c29bf4..5e83f39 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt @@ -12,7 +12,6 @@ import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.addJsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonArray From 0dd7be692390c13ca04802f016067a40cf178a2e Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh <115153463+yesshreyes@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:52:52 +0530 Subject: [PATCH 3/8] Removed extra lines --- gradle/libs.versions.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3993cbe..80c5c24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,6 @@ ktlint = "12.2.0" secrets = "2.0.1" jetbrainsKotlinJvm = "2.1.10" - [libraries] androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrowFxCoroutines" } @@ -54,7 +53,6 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging" } ktor-client-mock = { module = "io.ktor:ktor-client-mock" } ktor-client-serialization = { module = "io.ktor:ktor-client-serialization" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } - koin-android = { module = "io.insert-koin:koin-android" } koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose" } koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" } @@ -71,4 +69,4 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } googleSecrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } -jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } \ No newline at end of file +jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } From 0236d19c40ddf2165cb908ac994786073a5a5263 Mon Sep 17 00:00:00 2001 From: Raj Date: Mon, 9 Jun 2025 21:02:36 +0530 Subject: [PATCH 4/8] Update workflow with environmental variables + configure android sdk and properties --- .github/workflows/main.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index da02891..4cac4a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest env: - OPEN_API_KEY: ${{ secrets.OPEN_API_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} steps: - name: Checkout @@ -23,9 +23,18 @@ jobs: java-version: 17 cache: gradle + - name: Set up Android SDK + uses: android-actions/setup-android@v2 + with: + api-level: 33 + build-tools: "33.0.2" + - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Create local.properties + run: echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties + - name: Cache Gradle dependencies uses: actions/cache@v3 with: From faab7a20647c4072f0dc8e997e7abaea7b7915c7 Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh Date: Tue, 10 Jun 2025 10:08:42 +0530 Subject: [PATCH 5/8] Refactor `KtorHttpClient` for Gemini API interaction: - Introduce `GeminiApiService` for handling Gemini API requests. - Create `KtorClientProvider` to manage the Ktor `HttpClient` and `Json` instances. - Implement `GeminiPromptBuilder` to construct API request prompts and bodies. - Add `GeminiJsonParser` for extracting and cleaning JSON from API responses. - Move data models `CodeExample`, `Section`, and `Syntax` to separate files. - Remove unused OpenAI-related data classes from `KotlinTopicDetails.kt`. - Update `DetailRepository` to use the new `GeminiApiService`. --- .../core/network/KtorHttpClient.kt | 114 ------------------ .../core/network/api/GeminiApiService.kt | 38 ++++++ .../core/network/client/KtorClientProvider.kt | 20 +++ .../core/network/parser/GeminiJsonParser.kt | 32 +++++ .../network/request/GeminiPromptBuilder.kt | 95 +++++++++++++++ .../data/detail/model/CodeExample.kt | 10 ++ .../data/detail/model/KotlinTopicDetails.kt | 68 ----------- .../data/detail/model/Section.kt | 10 ++ .../data/detail/model/Syntax.kt | 9 ++ .../detail/repository/DetailRepository.kt | 4 +- 10 files changed, 216 insertions(+), 184 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/parser/GeminiJsonParser.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/request/GeminiPromptBuilder.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt deleted file mode 100644 index 5e83f39..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.core.network - -import com.developersbreach.kotlindictionarymultiplatform.Log -import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails -import io.ktor.client.HttpClient -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.contentType -import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.addJsonObject -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray - -object KtorHttpClient { - - private val json = Json { - ignoreUnknownKeys = true - prettyPrint = true - } - - private val client = HttpClient { - install(ContentNegotiation) { - json() - } - } - - suspend fun generateTopicDetails( - topicId: String, - apiKey: String, - ): KotlinTopicDetails { - // Gemini-style prompt - val prompt = """ - You are a Kotlin documentation generator. - Generate a JSON object for the topic "$topicId" with the following structure: - - { - "topicId": "...", - "topicName": "...", - "intro": "...", - "syntax": { - "signature": "...", - "notes": "..." - }, - "sections": [ - { - "heading": "...", - "content": "...", - "codeExamples": [ - { - "description": "...", - "code": "...", - "language": "kotlin" - } - ] - } - ], - "pitfalls": ["..."], - "relatedTopics": ["..."], - "metadata": {} - } - - Respond only with pure JSON. No explanation or markdown. - """.trimIndent() - - val requestBody = buildJsonObject { - putJsonArray("contents") { - addJsonObject { - putJsonArray("parts") { - addJsonObject { - put("text", prompt) - } - } - } - } - } - - val response = client.post("https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey") { - contentType(ContentType.Application.Json) - setBody(requestBody) - } - - val responseBody = response.bodyAsText() - Log.i("GeminiRawResponse", responseBody) - - // Parse root object - val root = json.parseToJsonElement(responseBody).jsonObject - val candidates = root["candidates"]?.jsonArray ?: error("Missing candidates") - val firstCandidate = candidates.first().jsonObject - val content = firstCandidate["content"]?.jsonObject ?: error("Missing content") - val partsArray = content["parts"]?.jsonArray ?: error("Missing parts array") - val part = partsArray.first().jsonObject - val rawJson = part["text"]?.jsonPrimitive?.content ?: error("Missing text in part") - Log.i("RawJson", rawJson) - - // Trim whitespace, remove code fences if any - val cleanJson = rawJson.trim() - .removePrefix("```json\n") - .removePrefix("```json") - .removePrefix("json\n") - .removePrefix("json") - .removeSuffix("```") - .trim() - - return json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt new file mode 100644 index 0000000..2e2af42 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt @@ -0,0 +1,38 @@ +package com.developersbreach.kotlindictionarymultiplatform.core.network.api + +import com.developersbreach.kotlindictionarymultiplatform.Log +import com.developersbreach.kotlindictionarymultiplatform.core.network.client.KtorClientProvider +import com.developersbreach.kotlindictionarymultiplatform.core.network.parser.GeminiJsonParser +import com.developersbreach.kotlindictionarymultiplatform.core.network.request.GeminiPromptBuilder +import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.contentType + +object GeminiApiService { + + suspend fun generateTopicDetails( + topicId: String, + apiKey: String, + ): KotlinTopicDetails { + val prompt = GeminiPromptBuilder.buildRequest(topicId) + val requestBody = GeminiPromptBuilder.buildRequestBody(prompt) + + val response = KtorClientProvider.client.post( + "https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey", + ) { + contentType(ContentType.Application.Json) + setBody(requestBody) + } + + val responseBody = response.bodyAsText() + Log.i("GeminiRawResponse", responseBody) + + val cleanJson = GeminiJsonParser.extractCleanJson(responseBody, KtorClientProvider.json) + Log.i("CleanJson", cleanJson) + + return KtorClientProvider.json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt new file mode 100644 index 0000000..520b2ec --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt @@ -0,0 +1,20 @@ +package com.developersbreach.kotlindictionarymultiplatform.core.network.client + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json + +object KtorClientProvider { + + val json = Json { + ignoreUnknownKeys = true + prettyPrint = true + } + + val client = HttpClient { + install(ContentNegotiation) { + json() + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/parser/GeminiJsonParser.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/parser/GeminiJsonParser.kt new file mode 100644 index 0000000..be4a28d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/parser/GeminiJsonParser.kt @@ -0,0 +1,32 @@ +package com.developersbreach.kotlindictionarymultiplatform.core.network.parser + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive + +object GeminiJsonParser { + + fun extractCleanJson( + rawResponse: String, + json: Json, + ): String { + val root = json.parseToJsonElement(rawResponse).jsonObject + val text = root["candidates"] + ?.jsonArray?.firstOrNull() + ?.jsonObject?.get("content") + ?.jsonObject?.get("parts") + ?.jsonArray?.firstOrNull() + ?.jsonObject?.get("text") + ?.jsonPrimitive?.content + ?: error("Malformed Gemini response") + + return text + .removePrefix("```json\n") + .removePrefix("```json") + .removePrefix("json\n") + .removePrefix("json") + .removeSuffix("```") + .trim() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/request/GeminiPromptBuilder.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/request/GeminiPromptBuilder.kt new file mode 100644 index 0000000..ec755ea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/request/GeminiPromptBuilder.kt @@ -0,0 +1,95 @@ +package com.developersbreach.kotlindictionarymultiplatform.core.network.request + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray + +object GeminiPromptBuilder { + + private val jsonSchema = """ + { + "type": "object", + "properties": { + "topicId": { "type": "string" }, + "topicName": { "type": "string" }, + "intro": { "type": "string" }, + "syntax": { + "type": "object", + "properties": { + "signature": { "type": "string" }, + "notes": { "type": "string" } + }, + "required": ["signature"] + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "heading": { "type": "string" }, + "content": { "type": "string" }, + "codeExamples": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { "type": "string" }, + "code": { "type": "string" }, + "language": { "type": "string" } + }, + "required": ["code"] + } + } + }, + "required": ["heading", "content"] + } + }, + "pitfalls": { "type": "array", "items": { "type": "string" } }, + "relatedTopics": { "type": "array", "items": { "type": "string" } }, + "metadata": { "type": "object", "additionalProperties": true } + }, + "required": ["topicId", "topicName", "intro", "syntax", "sections"] + } + """.trimIndent() + + fun buildRequest( + topicId: String, + ): String { + return """ + You are a Kotlin documentation generator. + + Generate a detailed JSON object for the topic "$topicId". + The output MUST strictly conform to the following JSON schema: + + $jsonSchema + + Requirements: + - Respond only with pure JSON. + - Do not include any Markdown formatting (no ```json). + - Ensure all required fields are present. + - Use valid Kotlin examples. + - Include at least 3 sections with headings, content, and code examples. + - Use the language "kotlin" in each code example. + + Make sure the entire response is a **valid JSON** object matching the schema above. + """.trimIndent() + } + + fun buildRequestBody( + prompt: String, + ): JsonObject { + return buildJsonObject { + putJsonArray("contents") { + addJsonObject { + putJsonArray("parts") { + addJsonObject { + put("text", prompt) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt new file mode 100644 index 0000000..beb97b2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt @@ -0,0 +1,10 @@ +package com.developersbreach.kotlindictionarymultiplatform.data.detail.model + +import kotlinx.serialization.Serializable + +@Serializable +data class CodeExample( + val description: String? = null, + val code: String, + val language: String = "kotlin", +) \ 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 7c4d944..d72fe77 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,6 +1,5 @@ package com.developersbreach.kotlindictionarymultiplatform.data.detail.model -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @@ -14,71 +13,4 @@ data class KotlinTopicDetails( val pitfalls: List = emptyList(), val relatedTopics: List = emptyList(), val metadata: Map = emptyMap(), -) - -@Serializable -data class Syntax( - val signature: String, - val notes: String? = null, -) - -@Serializable -data class Section( - val heading: String? = null, - val content: String? = null, - val codeExamples: List = emptyList(), -) - -@Serializable -data class CodeExample( - val description: String? = null, - val code: String, - val language: String = "kotlin", -) - -// --- Request/Response schema for OpenAI Chat Completion --- -@Serializable -data class ChatMessage( - val role: String, - val content: String, -) - -@Serializable -data class FunctionDefinition( - val name: String, - val description: String, - val parameters: JsonElement, -) - -@Serializable -data class FunctionCall( - val name: String, - val arguments: String, -) - -@Serializable -data class ChatCompletionChoice( - val message: ChatCompletionResponseMessage, -) - -@Serializable -data class ChatCompletionResponseMessage( - val role: String, - val content: String? = null, - @SerialName("function_call") - val functionCall: FunctionCall? = null, -) - -@Serializable -data class ChatCompletionResponse( - val choices: List?, -) - -@Serializable -data class ChatCompletionRequest( - val model: String, - val messages: List, - val functions: List, - @SerialName("function_call") - val functionCall: Map, ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt new file mode 100644 index 0000000..80b4e3f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt @@ -0,0 +1,10 @@ +package com.developersbreach.kotlindictionarymultiplatform.data.detail.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Section( + val heading: String? = null, + val content: String? = null, + val codeExamples: List = emptyList(), +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt new file mode 100644 index 0000000..0f121b3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt @@ -0,0 +1,9 @@ +package com.developersbreach.kotlindictionarymultiplatform.data.detail.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Syntax( + val signature: String, + val notes: String? = null, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt index a36ce7e..5f8b81c 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt @@ -1,7 +1,7 @@ package com.developersbreach.kotlindictionarymultiplatform.data.detail.repository import arrow.core.Either -import com.developersbreach.kotlindictionarymultiplatform.core.network.KtorHttpClient +import com.developersbreach.kotlindictionarymultiplatform.core.network.api.GeminiApiService import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails import com.developersbreach.kotlindictionarymultiplatform.getOpenApiKey @@ -11,7 +11,7 @@ class DetailRepository { topicId: String, ): Either { return Either.catch { - KtorHttpClient.generateTopicDetails( + GeminiApiService.generateTopicDetails( topicId = topicId, apiKey = getOpenApiKey(), ) From dad3e5a46653d3c0d5ae4c4ce989314c09a2959c Mon Sep 17 00:00:00 2001 From: Raj Date: Tue, 10 Jun 2025 12:47:46 +0530 Subject: [PATCH 6/8] Adding missing dependency injection in implementation separated in modules --- .../MainActivity.kt | 4 ++-- .../core/network/api/GeminiApiService.kt | 15 +++++++----- .../core/network/client/KtorClientProvider.kt | 20 ---------------- .../detail/repository/DetailRepository.kt | 6 +++-- .../di/ApiModule.kt | 10 ++++++++ .../di/AppModules.kt | 12 ++++++++++ .../di/HttpClientModule.kt | 24 +++++++++++++++++++ .../di/RepositoryModule.kt | 14 +++++++++++ .../di/{AppModule.kt => ViewModelModule.kt} | 11 +++------ 9 files changed, 78 insertions(+), 38 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ApiModule.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModules.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/HttpClientModule.kt create mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/RepositoryModule.kt rename composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/{AppModule.kt => ViewModelModule.kt} (66%) diff --git a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/MainActivity.kt index a5eac55..131c277 100644 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/MainActivity.kt @@ -5,7 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import com.developersbreach.kotlindictionarymultiplatform.di.appModule +import com.developersbreach.kotlindictionarymultiplatform.di.appModules import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -17,7 +17,7 @@ class MainActivity : ComponentActivity() { startKoin { androidContext(this@MainActivity) - modules(appModule) + appModules() } setContent { diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt index 2e2af42..3d7e2db 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt @@ -1,18 +1,21 @@ package com.developersbreach.kotlindictionarymultiplatform.core.network.api import com.developersbreach.kotlindictionarymultiplatform.Log -import com.developersbreach.kotlindictionarymultiplatform.core.network.client.KtorClientProvider import com.developersbreach.kotlindictionarymultiplatform.core.network.parser.GeminiJsonParser import com.developersbreach.kotlindictionarymultiplatform.core.network.request.GeminiPromptBuilder import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails +import io.ktor.client.HttpClient import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.contentType +import kotlinx.serialization.json.Json -object GeminiApiService { - +class GeminiApiService( + private val client: HttpClient, + private val json: Json, +) { suspend fun generateTopicDetails( topicId: String, apiKey: String, @@ -20,7 +23,7 @@ object GeminiApiService { val prompt = GeminiPromptBuilder.buildRequest(topicId) val requestBody = GeminiPromptBuilder.buildRequestBody(prompt) - val response = KtorClientProvider.client.post( + val response = client.post( "https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey", ) { contentType(ContentType.Application.Json) @@ -30,9 +33,9 @@ object GeminiApiService { val responseBody = response.bodyAsText() Log.i("GeminiRawResponse", responseBody) - val cleanJson = GeminiJsonParser.extractCleanJson(responseBody, KtorClientProvider.json) + val cleanJson = GeminiJsonParser.extractCleanJson(responseBody, json) Log.i("CleanJson", cleanJson) - return KtorClientProvider.json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson) + return json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt deleted file mode 100644 index 520b2ec..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/client/KtorClientProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.core.network.client - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.json.Json - -object KtorClientProvider { - - val json = Json { - ignoreUnknownKeys = true - prettyPrint = true - } - - val client = HttpClient { - install(ContentNegotiation) { - json() - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt index 5f8b81c..4c4a0d1 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/repository/DetailRepository.kt @@ -5,13 +5,15 @@ import com.developersbreach.kotlindictionarymultiplatform.core.network.api.Gemin import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails import com.developersbreach.kotlindictionarymultiplatform.getOpenApiKey -class DetailRepository { +class DetailRepository( + private val service: GeminiApiService, +) { suspend fun fetchTopic( topicId: String, ): Either { return Either.catch { - GeminiApiService.generateTopicDetails( + service.generateTopicDetails( topicId = topicId, apiKey = getOpenApiKey(), ) diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ApiModule.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ApiModule.kt new file mode 100644 index 0000000..571ecb0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ApiModule.kt @@ -0,0 +1,10 @@ +package com.developersbreach.kotlindictionarymultiplatform.di + +import com.developersbreach.kotlindictionarymultiplatform.core.network.api.GeminiApiService +import org.koin.dsl.module + +internal val apiModule = module { + single { + GeminiApiService(client = get(), json = get()) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModules.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModules.kt new file mode 100644 index 0000000..9106915 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModules.kt @@ -0,0 +1,12 @@ +package com.developersbreach.kotlindictionarymultiplatform.di + +import org.koin.core.KoinApplication + +internal fun KoinApplication.appModules() { + modules( + httpClientModule, + apiModule, + repositoryModule, + viewModelModule, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/HttpClientModule.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/HttpClientModule.kt new file mode 100644 index 0000000..c028ada --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/HttpClientModule.kt @@ -0,0 +1,24 @@ +package com.developersbreach.kotlindictionarymultiplatform.di + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import org.koin.dsl.module + +internal val httpClientModule = module { + single { + Json { + ignoreUnknownKeys = true + prettyPrint = true + } + } + + single { + HttpClient { + install(ContentNegotiation) { + json(get()) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/RepositoryModule.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/RepositoryModule.kt new file mode 100644 index 0000000..18a16fc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/RepositoryModule.kt @@ -0,0 +1,14 @@ +package com.developersbreach.kotlindictionarymultiplatform.di + +import com.developersbreach.kotlindictionarymultiplatform.data.detail.repository.DetailRepository +import com.developersbreach.kotlindictionarymultiplatform.data.topic.repository.TopicRepository +import org.koin.dsl.module + +internal val repositoryModule = module { + single { + DetailRepository(get()) + } + single { + TopicRepository + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModule.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ViewModelModule.kt similarity index 66% rename from composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModule.kt rename to composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ViewModelModule.kt index 410916b..909a263 100644 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/AppModule.kt +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/di/ViewModelModule.kt @@ -1,17 +1,12 @@ package com.developersbreach.kotlindictionarymultiplatform.di -import com.developersbreach.kotlindictionarymultiplatform.data.detail.repository.DetailRepository -import org.koin.dsl.module +import androidx.lifecycle.SavedStateHandle import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailViewModel import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicViewModel import org.koin.core.module.dsl.viewModel -import androidx.lifecycle.SavedStateHandle -import com.developersbreach.kotlindictionarymultiplatform.data.topic.repository.TopicRepository - -val appModule = module { - single { DetailRepository() } - single { TopicRepository } +import org.koin.dsl.module +internal val viewModelModule = module { viewModel { (handle: SavedStateHandle) -> DetailViewModel( savedStateHandle = handle, From 1b868bf2cfc8dc1f596ac1f1c79758278cf08f9d Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh Date: Tue, 10 Jun 2025 16:01:20 +0530 Subject: [PATCH 7/8] Moved `CodeExample`, `Section`, and `Syntax` data classes into `KotlinTopicDetails.kt`. --- .../data/detail/model/CodeExample.kt | 10 ---------- .../data/detail/model/KotlinTopicDetails.kt | 20 +++++++++++++++++++ .../data/detail/model/Section.kt | 10 ---------- .../data/detail/model/Syntax.kt | 9 --------- 4 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt deleted file mode 100644 index beb97b2..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/CodeExample.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.data.detail.model - -import kotlinx.serialization.Serializable - -@Serializable -data class CodeExample( - val description: String? = null, - val code: String, - val language: String = "kotlin", -) \ 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 d72fe77..5694efc 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 @@ -13,4 +13,24 @@ data class KotlinTopicDetails( val pitfalls: List = emptyList(), val relatedTopics: List = emptyList(), val metadata: Map = emptyMap(), +) + +@Serializable +data class CodeExample( + val description: String? = null, + val code: String, + val language: String = "kotlin", +) + +@Serializable +data class Section( + val heading: String? = null, + val content: String? = null, + val codeExamples: List = emptyList(), +) + +@Serializable +data class Syntax( + val signature: String, + val notes: String? = null, ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt deleted file mode 100644 index 80b4e3f..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Section.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.data.detail.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Section( - val heading: String? = null, - val content: String? = null, - val codeExamples: List = emptyList(), -) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt deleted file mode 100644 index 0f121b3..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/Syntax.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.developersbreach.kotlindictionarymultiplatform.data.detail.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Syntax( - val signature: String, - val notes: String? = null, -) \ No newline at end of file From 15fc88833e787c83fed78e47c305e9796e3e9752 Mon Sep 17 00:00:00 2001 From: Shreyas Deshmukh Date: Tue, 10 Jun 2025 16:07:18 +0530 Subject: [PATCH 8/8] Mark "Replace search icon" and "Switch AI integration to Gemini" tasks as complete in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 077003f..fc59f43 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ This is an educational project developed by students. Expect rapid changes, expe - [ ] Introduce common @Preview annotations for reusable Composable previews. - [ ] Implement section-wise scroll behavior on the `Detail Screen` for better navigation. - [ ] Add inline code formatting support for syntax display on the `Detail Screen`. -- [ ] Replace the search icon on the `Topic Card` to improve visual consistency. +- [x] Replace the search icon on the `Topic Card` to improve visual consistency. - [ ] Implement caching on the `Detail Screen` to store previously viewed topic data. -- [ ] Switch AI integration from OpenAI to Gemini. +- [x] Switch AI integration from OpenAI to Gemini. - [ ] Refactor network layer for cleaner architecture.