Skip to content

Commit

Permalink
Merge pull request #14 from percy-g2/main
Browse files Browse the repository at this point in the history
UI improvements and code refactoring
  • Loading branch information
joreilly authored Jan 22, 2024
2 parents 6677736 + 9fd413b commit bd6eae0
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 100 deletions.
11 changes: 4 additions & 7 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -134,26 +134,23 @@ compose.desktop {
}
}

//val localProperties = Properties()
//localProperties.load(rootProject.file("local.properties").reader())

buildkonfig {
packageName = "dev.johnoreilly.gemini"

val localPropsFile = rootProject.file("local.properties")
println(localPropsFile)
val localProperties = Properties()
if (localPropsFile.exists()) {
try {
runCatching {
localProperties.load(localPropsFile.inputStream())
} catch (e: Exception) {
}.getOrElse {
it.printStackTrace()
}
}
defaultConfigs {
buildConfigField(
FieldSpec.Type.STRING,
"GEMINI_API_KEY",
localProperties["gemini_api_key"]?.toString() ?: "abc"
localProperties["gemini_api_key"]?.toString() ?: ""
)
}

Expand Down
89 changes: 62 additions & 27 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -29,37 +37,55 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlin.io.encoding.ExperimentalEncodingApi


@OptIn(ExperimentalEncodingApi::class, ExperimentalLayoutApi::class)
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun App() {
val api = remember { GeminiApi() }

val coroutineScope = rememberCoroutineScope()
var prompt by remember { mutableStateOf("") }
var selectedImageData by remember { mutableStateOf<ByteArray?>(null) }
var content by remember { mutableStateOf("") }
var showProgress by remember { mutableStateOf(false) }
var showImagePicker by remember { mutableStateOf(false) }

var filePath by remember { mutableStateOf("") }

var image by remember { mutableStateOf<ImageBitmap?>(null) }
val canClearPrompt by remember {
derivedStateOf {
prompt.isNotBlank()
}
}

MaterialTheme {
Column(
Modifier
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth().padding(16.dp)
) {
FlowRow {
TextField(
OutlinedTextField(
value = prompt,
onValueChange = { prompt = it },
modifier = Modifier.weight(7f)
modifier = Modifier
.fillMaxSize()
.defaultMinSize(minHeight = 52.dp),
label = {
Text("Search")
},
trailingIcon = {
if (canClearPrompt) {
IconButton(
onClick = { prompt = "" }
) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = "Clear"
)
}
}
}
)

OutlinedButton(
onClick = {
if (prompt.isNotBlank()) {
Expand All @@ -68,13 +94,13 @@ fun App() {
.onStart { showProgress = true }
.onCompletion { showProgress = false }
.collect { content += it.text }
println(content)
}
}
},
enabled = prompt.isNotBlank(),
modifier = Modifier
.weight(3f)
.padding(all = 4.dp)
.weight(1f)
.align(Alignment.CenterVertically)
) {
Text("Submit")
Expand All @@ -83,8 +109,8 @@ fun App() {
OutlinedButton(
onClick = { showImagePicker = true },
modifier = Modifier
.weight(3f)
.padding(all = 4.dp)
.weight(1f)
.align(Alignment.CenterVertically)
) {
Text("Select Image")
Expand All @@ -101,33 +127,42 @@ fun App() {
}

Spacer(Modifier.height(16.dp))
image?.let {
Image(
painter = BitmapPainter(it),
contentDescription = "",
modifier = Modifier.height(200.dp)
)

image?.let { imageBitmap ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = BitmapPainter(imageBitmap),
contentDescription = "search_image",
modifier = Modifier.fillMaxSize()
)
}
}

Spacer(Modifier.height(16.dp))
if (showProgress) {
CircularProgressIndicator()
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
} else {
GeminiMarkdown(content)
}
}
}
}


fun generateContentAsFlow(
api: GeminiApi,
prompt: String,
imageData: ByteArray? = null
): Flow<GenerateContentResponse> {
return if (imageData != null) {
api.generateContent(prompt, imageData)
} else {
api.generateContent(prompt)
}
): Flow<GenerateContentResponse> = imageData?.let { imageByteArray ->
api.generateContent(prompt, imageByteArray)
} ?: run {
api.generateContent(prompt)
}
14 changes: 2 additions & 12 deletions composeApp/src/desktopMain/kotlin/actual.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.ImageBitmap
Expand All @@ -7,22 +6,14 @@ import com.darkrockstudios.libraries.mpfilepicker.FilePicker
import com.mikepenz.markdown.m3.Markdown
import kotlinx.coroutines.launch
import org.jetbrains.skia.Image
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi



actual fun ByteArray.toComposeImageBitmap(): ImageBitmap {
return Image.makeFromEncoded(this).toComposeImageBitmap()
}

actual fun ByteArray.toComposeImageBitmap(): ImageBitmap = Image.makeFromEncoded(this).toComposeImageBitmap()

@Composable
actual fun GeminiMarkdown(content: String) {
Markdown(content)
}

@OptIn(ExperimentalEncodingApi::class)
@Composable
actual fun ImagePicker(
show: Boolean,
Expand All @@ -35,8 +26,7 @@ actual fun ImagePicker(
val fileExtensions = listOf("jpg", "png")
FilePicker(show = show, fileExtensions = fileExtensions) { file ->
coroutineScope.launch {
val imageData = file?.getFileByteArray()
imageData?.let {
file?.getFileByteArray()?.let { imageData ->
onImageSelected(file.path, imageData)
}
}
Expand Down
49 changes: 0 additions & 49 deletions composeApp/src/wasmJsMain/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,58 +1,9 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.CanvasBasedWindow

import kotlinx.coroutines.await
import kotlinx.coroutines.launch
import kotlin.js.Promise


@OptIn(ExperimentalComposeUiApi::class)
fun main() {


CanvasBasedWindow(canvasElementId = "ComposeTarget") {
App()

/*
val coroutineScope = rememberCoroutineScope()
val api = remember { GeminiApi() }
var text by remember { mutableStateOf("") }
Column {
Text(text)
TextButton(
onClick = {
coroutineScope.launch {
val imageData = importImageFile() ?: ""
val response = api.generateContent("What is this picture?", imageData)
text = response.toString()
}
},
modifier = Modifier
.weight(3f)
.padding(all = 4.dp)
) {
Text("Pick File")
}
}
*/
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
agp = "8.2.0"
agp = "8.2.1"
android-compileSdk = "34"
android-minSdk = "24"
android-targetSdk = "34"
Expand Down
8 changes: 4 additions & 4 deletions wearApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ android {
defaultConfig {
applicationId = "dev.johnoreilly.gemini"
minSdk = 30
targetSdk = 33
targetSdk = 34
versionCode = 1
versionName = "1.0"

val localPropsFile = rootProject.file("local.properties")
println(localPropsFile)
val localProperties = Properties()
if (localPropsFile.exists()) {
try {
runCatching {
localProperties.load(localPropsFile.inputStream())
} catch (e: Exception) {
}.getOrElse {
it.printStackTrace()
}
}

Expand Down

0 comments on commit bd6eae0

Please sign in to comment.