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: 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. diff --git a/build.gradle.kts b/build.gradle.kts index da790f8..5b452ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,5 +7,6 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.serialization) apply false + alias(libs.plugins.googleSecrets) apply false alias(libs.plugins.jetbrainsKotlinJvm) apply false } \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index adc9d32..0840d4d 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 { @@ -75,6 +76,7 @@ kotlin { implementation(libs.androidx.activity.compose) implementation(libs.koin.android) implementation(libs.koin.androidx.compose) + implementation(libs.generativeai) } commonMain.dependencies { implementation(compose.runtime) @@ -177,14 +179,14 @@ fun ApplicationDefaultConfig.setupBuildConfigFields( ) } - 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")}\"", + name = "GEMINI_API_KEY", + value = "\"${secret("GEMINI_API_KEY")}\"", ) } 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/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt index b44e136..a5548f2 100644 --- a/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt +++ b/composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/Platform.android.kt @@ -11,5 +11,5 @@ actual fun getPlatform(): Platform { } actual fun getOpenApiKey(): String { - return BuildConfig.OPEN_API_KEY + return 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 deleted file mode 100644 index d7f0132..0000000 --- a/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt +++ /dev/null @@ -1,136 +0,0 @@ -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 - -object KtorHttpClient { - private val json = Json { ignoreUnknownKeys = true } - - private val client = HttpClient { - install(ContentNegotiation) { - json() - } - } - - 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\".", - ), - ) - - // Build request body - val request = ChatCompletionRequest( - model = "gpt-4o-mini", - messages = messages, - functions = listOf(functionDef), - functionCall = mapOf("name" to functionDef.name), - ) - - // Execute HTTP request - val response: HttpResponse = client.post("https://api.openai.com/v1/chat/completions") { - header(HttpHeaders.Authorization, "Bearer $apiKey") - contentType(ContentType.Application.Json) - setBody(json.encodeToString(ChatCompletionRequest.serializer(), request)) - } - - val text = response.bodyAsText() - Log.i("RawResponse", "RAW RESPONSE:\n$text") - - // 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") - - // The arguments field is a JSON string: parse and decode into our DTO - val argsJson = json.parseToJsonElement(funcCall.arguments) - return json.decodeFromJsonElement(argsJson.jsonObject) - } -} \ 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..3d7e2db --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/api/GeminiApiService.kt @@ -0,0 +1,41 @@ +package com.developersbreach.kotlindictionarymultiplatform.core.network.api + +import com.developersbreach.kotlindictionarymultiplatform.Log +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 + +class GeminiApiService( + private val client: HttpClient, + private val json: Json, +) { + suspend fun generateTopicDetails( + topicId: String, + apiKey: String, + ): KotlinTopicDetails { + val prompt = GeminiPromptBuilder.buildRequest(topicId) + val requestBody = GeminiPromptBuilder.buildRequestBody(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) + + val cleanJson = GeminiJsonParser.extractCleanJson(responseBody, json) + Log.i("CleanJson", 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/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/KotlinTopicDetails.kt b/composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/detail/model/KotlinTopicDetails.kt index 7c4d944..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 @@ -1,6 +1,5 @@ package com.developersbreach.kotlindictionarymultiplatform.data.detail.model -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @@ -16,19 +15,6 @@ data class KotlinTopicDetails( 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, @@ -36,49 +22,15 @@ data class CodeExample( 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, +data class Section( + val heading: String? = null, val content: String? = null, - @SerialName("function_call") - val functionCall: FunctionCall? = null, -) - -@Serializable -data class ChatCompletionResponse( - val choices: List?, + val codeExamples: List = emptyList(), ) @Serializable -data class ChatCompletionRequest( - val model: String, - val messages: List, - val functions: List, - @SerialName("function_call") - val functionCall: Map, +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..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 @@ -1,17 +1,19 @@ 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 -class DetailRepository { +class DetailRepository( + private val service: GeminiApiService, +) { suspend fun fetchTopic( topicId: String, ): Either { return Either.catch { - KtorHttpClient.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, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ba4bc9..80c5c24 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,12 +23,14 @@ ktor-bom = "3.0.1" koin = "4.0.4" navigation-compose = "2.9.0" 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" } 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" } @@ -50,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" } @@ -66,4 +68,5 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k 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" } -jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } \ No newline at end of file +googleSecrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } +jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }