* feat(#195): add chat attachment support for files and images - Add Attachment data model with computed properties (isImage, formattedSize, fileExtension) - Extend ChatMessage with optional attachments list - Add Coil 2.7.0 for async image loading in Compose - Extend ChatViewModel with pending attachment state, addAttachment/removeAttachment/clearAttachments - Build attachment-prefixed message text (data URIs for images, file refs for others) - Add paperclip button and file picker launcher (ActivityResultContracts.GetContent) to ChatScreen - Add animated attachment preview chips with thumbnails before sending - Render attachments inline in UserBubble (AsyncImage for images, file cards for others) - Add string resources for attachment UI features * fix(#195): rewrite attachment upload protocol to match Hermes backend RPC methods BREAKS: removes old buildAttachmentText() that embedded base64 data URIs in prompt text. Changes: - Replace inline data-URI encoding with proper backend RPC flow: - image.attach_bytes for images (fire-and-forget, auto-queues to session) - file.attach for non-image files (awaited, returns @file: ref text) - Add CompletableDeferred to PendingRequest so sendMessage can await RPC responses - Add readContentUriBytes() to read file content from content:// URIs - Add sendRpcAndAwait() suspend function for sequential RPC calls - Add RpcCallException for awaited RPC error handling - Suppress error UI for awaited RPCs (caller handles them directly) - Keep ChatScreen/ChatBubble UI layer unchanged (file picker, previews, inline images) * feat(#195): add camera capture support via FileProvider + TakePicture contract - Creates temporary photo files in cacheDir via FileProvider - Launches system camera with ActivityResultContracts.TakePicture - Adds camera icon button (📷) next to the paperclip in ChatInputBar - Captured photos added as image/jpeg attachments via same addAttachment flow - FileProvider configured with cache-root path in res/xml/file_paths.xml - Error handling for launch failures and capture failures with snackbar * refactor(#195): group camera/image/file into single attach dropdown - Replaces separate 📷 and 📎 buttons with a single 📎 button - DropdownMenu opens with three options: - 📷 Camera → TakePicture via FileProvider temp URI - 🖼️ Image → GetContent("image/*") - 📄 File → GetContent("*/*") - Removes onAttachClick, adds onImageTap/onFileTap callbacks - Input bar stays clean with one attachment affordance * fix(#195): add missing imports for CI compilation ChatScreen.kt: - android.util.Log - androidx.compose.animation.core.* (infiniteRepeatable, tween, LinearEasing, RepeatMode, rememberInfiniteTransition) - androidx.compose.foundation.layout.widthIn - androidx.compose.material.icons.filled.AttachFile - androidx.compose.material.icons.filled.InsertDriveFile - androidx.compose.ui.layout.ContentScale - coil.compose.AsyncImage ChatBubble.kt: - androidx.compose.material.icons.filled.InsertDriveFile * fix(#195): resolve android-lint LocalContextGetResourceValueCall error Replace context.getString(R.string.chat_camera_error) with a composable-level cameraErrorMsg captured via stringResource() to avoid querying resources via LocalContext.current in a callback. * fix(#195): migrate InsertDriveFile to AutoMirrored, fix chip double-remove - Replace deprecated Icons.Filled.InsertDriveFile with Icons.AutoMirrored.Filled.InsertDriveFile in ChatBubble.kt and ChatScreen.kt - Remove onClick from AttachmentChip Surface so only the dedicated close button triggers removal, not accidental taps on the chip body --------- Co-authored-by: Hermes Agent <hermes@agent.local>
89 lines
5.1 KiB
TOML
89 lines
5.1 KiB
TOML
[versions]
|
|
androidGradlePlugin = "9.0.1"
|
|
androidxCore = "1.18.0"
|
|
androidxLifecycle = "2.10.0"
|
|
androidxActivity = "1.13.0"
|
|
androidxComposeBom = "2026.03.01"
|
|
androidxTest = "1.7.0"
|
|
androidxTestExt = "1.3.0"
|
|
androidxTestRunner = "1.7.0"
|
|
androidxTestEspresso = "3.7.0"
|
|
coroutines = "1.11.0"
|
|
junit = "4.13.2"
|
|
junit5 = "6.1.0"
|
|
junitPlatform = "6.1.0"
|
|
kotlin = "2.4.0"
|
|
# standalone per KSP 2.0+ guidance — NOT kotlinVersion-kspVersion; easy
|
|
# to miss during Kotlin upgrades since it no longer tracks the Kotlin version.
|
|
ksp = "2.3.9"
|
|
nav3Core = "1.0.1"
|
|
lifecycleViewmodelNav3 = "2.10.0"
|
|
leakcanary = "2.14"
|
|
okhttp = "5.4.0"
|
|
retrofit = "3.0.0"
|
|
gson = "2.14.0"
|
|
securityCrypto = "1.0.0"
|
|
mockk = "1.14.11"
|
|
coil = "2.7.0"
|
|
room = "2.7.1"
|
|
sqlcipher = "4.16.0"
|
|
|
|
[libraries]
|
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
|
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
|
|
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3"}
|
|
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
|
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui"}
|
|
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview"}
|
|
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4"}
|
|
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling"}
|
|
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest"}
|
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
|
|
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
|
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
|
|
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTest" }
|
|
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExt" }
|
|
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxTestEspresso" }
|
|
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
|
|
junit = { module = "junit:junit", version.ref = "junit" }
|
|
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit5" }
|
|
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" }
|
|
junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" }
|
|
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" }
|
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
|
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
|
|
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
|
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
|
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
|
|
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
|
|
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
|
|
|
|
# Networking
|
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
|
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
|
|
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
|
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
|
|
|
# Security
|
|
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
|
|
|
|
# Database (Room)
|
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
|
sqlcipher = { module = "net.zetetic:sqlcipher-android", version.ref = "sqlcipher" }
|
|
|
|
# Image loading
|
|
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
|
|
|
|
|
[plugins]
|
|
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
|
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
room = { id = "androidx.room", version.ref = "room" }
|
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|